Почему subquery и join так медленно

мне нужно выбрать строки из таблицы BUNDLES, которые имеют одно из нескольких значений SAP_STATE_ID. Эти значения зависят от того, предполагается ли экспортировать соответствующий статус SAP или нет.

этот запрос выполняется очень быстро (есть индекс в поле SAP_STATE_ID) -

SELECT b.* FROM BUNDLES b WHERE b.SAP_STATE_ID IN (2,3,5,6)

но... Я хотел бы получить список идентификаторов динамически, например:

SELECT b.* FROM BUNDLES b 
WHERE b.SAP_STATE_ID IN 
(SELECT s.SAP_STATE_ID FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1)

и Ой, этот запрос внезапно занимает слишком много времени. Я бы ожидал, что SQL server сначала запустит подзапрос (это не зависит ни от чего из основного запроса), а затем запустите все, как в моем первом примере. Я попытался переписать его, чтобы использовать соединения вместо подзапроса:

SELECT b.* FROM BUNDLES b 
JOIN SAP_STATES s ON (s.SAP_STATE_ID = b.SAP_STATE_ID) 
WHERE s.EXPORT_TO_SAP = 1

но он имеет такую же плохую производительность. Похоже, что он запускает подзапрос для каждой строки таблицы BUNDLES или что-то в этом роде. Я не очень хорошо разбираюсь в планах исполнения, но я пытался. В нем говорится, что 81% стоимости-для сканирования индекса первичного ключа пакетов (я понятия не имею, почему он должен делать такую вещь, там поле BUNDLE_ID определено как первичный ключ, но оно вообще не отображается в запросе...)

у кого-нибудь есть объяснение, почему SQL server настолько "глупый"? Есть ли способ достичь того, что я хочу с хорошей производительностью, но без необходимости предоставлять статический список SAP_STATE_IDs?

скрипт для обеих таблиц и соответствующих индексов -http://mab.to/xbYiI0wKj

план выполнения подзапроса версия - http://mab.to/8Qh6gpdYZ

план запроса для версии с соединениями -http://mab.to/YCqeGCUbr

(по какой-то причине эти два плана выглядят одинаково, и оба предлагают создавать пакеты.Индекс SAP_STATE_ID, который уже есть)

3 ответов


Я уверен, что ваша статистика на столы. Если вы хотите, чтобы он работал в спешке, я бы написал запрос как:

SELECT b.*
  FROM SAP_STATES s 
 INNER LOOP JOIN BUNDLES b 
    ON s.SAP_STATE_ID = b.SAP_STATE_ID
 WHERE s.EXPORT_TO_SAP = 1

это заставляет вложенные петли присоединиться к SAP_STATES какие фильтры на BUNDLES


когда вы используете таблицы (временные или физические), SQL engine строит статистику против него и, таким образом, имеет очень четкое представление о количестве строк в нем и который является лучшим подходом к выполнению для него. С другой стороны, вычисляемая таблица(суб-запрос) не имеет статистики против нее.

поэтому, хотя человеку может показаться простым вывести количество строк в нем," глупый " SQL-движок не знает обо всем этом. Теперь, переходя к вопросу,WHERE s.EXPORT_TO_SAP = 1 предложение делает разница здесь. Кластеризованный индекс сортируется и строится на SAP_STATE_ID, но для дополнительной проверки предложения WHERE у него нет опции, кроме сканирования всей таблицы(в конечном наборе данных)! Я уверен, что если бы вместо кластеризованного индекса, если бы был некластеризованный покрытый индекс в столбце SAP_STATE_ID, который покрывал поле EXPORT_TO_SAP, это могло бы сделать трюк. Поскольку кластеризованные индексные проверки обычно плохи для производительности, я бы предложил вам принять следующее подход:

SELECT s.SAP_STATE_ID 
into #Sap_State
FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1

SELECT b.* FROM BUNDLES b 
join #Sap_State a on a.sap_state_id = b.sap_state_id

Так как по какой-то причине возникли проблемы с переходом на mab.to,

Я бы предложил обеспечить следующее

table        index
sap_states   (export_to_sap, sap_state_id )
bundles      (sap_state_id)

select
      b.*
   from 
      sap_states ss
         join bundles b
            on ss.sap_state_id = b.sap_state_id
   where 
      ss.export_to_sap = 1