Сомнительная производительность при использовании IF EXISTS с проверками внутреннего существования

#sql-server-2008-r2

#sql-server-2008-r2

Вопрос:

Это в хранимой процедуре..Это утверждение if, после чего я немного поработаю. @AsOfDate — это переданная переменная типа данных date. У меня вопрос в том, почему я получаю лучшую производительность, удаляя inner-most exists, но ТОЛЬКО тогда, когда весь оператор находится в IF EXISTS?

Две таблицы:

  • dbo.TXXX_InventoryDetail — 1,3 миллиарда записей .. статистика обновлена
  • dbo.TXXX_InventoryFull — 9,8 миллионов записей .. статистика обновлена

Инструкция:

 if exists (select 1
             from dbo.TXXX_InventoryDetail o
            where exists (select 1
                           from dbo.TXXX_InventoryFull i
                          where i.C001_AsOfDate= o.C001_AsOfDate
                            and i.C001_ProductID=o.C001_ProductID
                            and i.C001_StoreNumber=o.C001_StoreNumber
                            and i.C001_AsOfDate=@AsOfDate
                            and (i.C001_LastModelDate!=o.C001_LastModelDate
                                  or o.C001_InventoryQty!=o.C001_InventoryQty
                                  or i.C001_OnOrderQty!=o.C001_OnOrderQty
                                  or i.C001_TBOQty!=o.C001_TBOQty
                                  or i.C001_ModelQty!=o.C001_ModelQty
                                  or i.C001_TBOAdjustQty!=o.C001_TBOAdjustQty
                                  or i.C001_ReturnQtyPending!=o.C001_ReturnQtyPending
                                  or i.C001_ReturnQtyInProcess!=o.C001_ReturnQtyInProcess
                                  or i.C001_ReturnQtyDueOut!=o.C001_ReturnQtyDueOut))
              and o.C001_AsOfDate=@AsOfDate)
  

вывод ввода-вывода:

  • Таблица ‘TXXX_InventoryFull’. Количество сканирований 9240262, логические чтения 29548864
  • Таблица ‘T001_InventoryDetail’. Количество проверок 1, логические чтения 17259

Если я удалю второе, где существует, и выполню объединение:

  if exists (select 1
             from dbo.TXXX_InventoryDetail o,
                  dbo.TXXX_InventoryFull i
            where i.C001_AsOfDate= o.C001_AsOfDate
                            and i.C001_ProductID=o.C001_ProductID
                            and i.C001_StoreNumber=o.C001_StoreNumber
                            and i.C001_AsOfDate=@AsOfDate
                            and (i.C001_LastModelDate!=o.C001_LastModelDate
                                  or o.C001_InventoryQty!=o.C001_InventoryQty
                                  or i.C001_OnOrderQty!=o.C001_OnOrderQty
                                  or i.C001_TBOQty!=o.C001_TBOQty
                                  or i.C001_ModelQty!=o.C001_ModelQty
                                  or i.C001_TBOAdjustQty!=o.C001_TBOAdjustQty
                                  or i.C001_ReturnQtyPending!=o.C001_ReturnQtyPending
                                  or i.C001_ReturnQtyInProcess!=o.C001_ReturnQtyInProcess
                                  or i.C001_ReturnQtyDueOut!=o.C001_ReturnQtyDueOut)
              and o.C001_AsOfDate=@AsOfDate)
  

вывод ввода-вывода:

  • Таблица ‘TXXX_InventoryDetail’. Количество проверок 0, логические чтения 333952
  • Таблица ‘TXXX_InventoryFull’. Количество проверок 1, логические чтения 630

Теперь .. причина, по которой я думаю, что это if exists, заключается в том, что если я удалю его и выполню select count (*) следующим образом:

 select COUNT(*)
             from dbo.T001_InventoryDetail o
            where exists (select 1
                           from dbo.TXXX_InventoryFull i
                          where i.C001_AsOfDate= o.C001_AsOfDate
                            and i.C001_ProductID=o.C001_ProductID
                            and i.C001_StoreNumber=o.C001_StoreNumber
                            and i.C001_AsOfDate=@AsOfDate
                            and (i.C001_LastModelDate!=o.C001_LastModelDate
                                  or o.C001_InventoryQty!=o.C001_InventoryQty
                                  or i.C001_OnOrderQty!=o.C001_OnOrderQty
                                  or i.C001_TBOQty!=o.C001_TBOQty
                                  or i.C001_ModelQty!=o.C001_ModelQty
                                  or i.C001_TBOAdjustQty!=o.C001_TBOAdjustQty
                                  or i.C001_ReturnQtyPending!=o.C001_ReturnQtyPending
                                  or i.C001_ReturnQtyInProcess!=o.C001_ReturnQtyInProcess
                                  or i.C001_ReturnQtyDueOut!=o.C001_ReturnQtyDueOut))
              and o.C001_AsOfDate=@AsOfDate
  
  • TXXX_InventoryFull’. Количество проверок 41, логические чтения 692
  • T001_InventoryDetail’. Количество проверок 65, логические чтения 17477
  • Рабочий стол’. Количество проверок 0, логические чтения 0

Ответ №1:

Обычно говорят, что следует избегать выполнения скоординированных подзапросов в предикате, поскольку они имеют тенденцию вызывать соединения с вложенным циклом. При запросе больших наборов данных, особенно когда кто-то пытается обнаружить разницу между наборами, важно разрешить оптимизатору запросов динамически выбирать между алгоритмами хэша, слияния и вложенного цикла, что может быть невозможно, если запрос структурирован с использованием скоординированного подзапроса. Лучше создавать их как производные таблицы в предложении FROM .

Ответ №2:

Я обнаружил похожие проблемы при использовании инструкции EXISTS на сервере SQL 08 R2, где точно такая же инструкция отлично выполняется на SQL 08 и SQL 05.

Я обнаружил, что изменение чего-то вроде

 WHILE EXISTS(SELECT * FROM X)
  

Было бы очень медленно, но:

 WHILE ISNULL((SELECT TOP 1 ID FROM X), 0) <> 0
  

Снова работает идеально быстро.

Мне кажется, что это проблема R2…

Ответ №3:

Я бы предположил, что план, который вы получаете, сильно отличается при использовании join. Возможно, дисбаланс в количестве строк (очень большая внешняя таблица, внутренняя таблица меньшего размера) затрудняет работу оптимизатора, но, вероятно, с помощью join можно намного проще удалять строки (вероятно, вы увидите дополнительные операторы цикла с худшим запросом). Сложно предположить, не видя планов или не имея возможности воспроизвести, но вы всегда должны стремиться к удалению большинства строк как можно раньше в плане. Удаление миллионов строк с помощью нескольких операторов / подзапросов только для того, чтобы исключить большинство из них позже в плане, почти наверняка приведет к снижению производительности.