Почему ориентировочная стоимость (одинаковых) 1000 поисков по уникальному индексу отличается в этих планах?


28

В нижеприведенных запросах оба плана выполнения оцениваются в 1000 запросов на уникальный индекс.

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

Обе вложенные циклы имеют <NestedLoops Optimized="false" WithOrderedPrefetch="true">

Кто-нибудь знает, почему эта задача стоит 0,172434 в первом плане, а 3,01702 во втором?

(Причина вопроса в том, что первый запрос был предложен мне в качестве оптимизации из-за очевидной гораздо более низкой стоимости плана. На самом деле мне кажется, что он выполняет больше работы, но я просто пытаюсь объяснить несоответствие .. .)

Настроить

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

Запрос 1 "Вставить план" ссылка

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

Запрос 2 "Вставить план" ссылка

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

Запрос 1

Запрос 2

Выше было проверено на SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)


@Joe Obbish указывает в комментариях, что было бы проще

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

против

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

Для промежуточной таблицы из 1000 строк оба из перечисленных выше по-прежнему имеют одну и ту же форму плана с вложенными циклами, а план без производной таблицы, которая выглядит дешевле, но для промежуточной таблицы из 10 000 строк и той же целевой таблицы, что и выше, разница в стоимости действительно меняет план форма (с полным сканированием и объединением слиянием, кажущееся относительно более привлекательным, чем поиск с дорогой стоимостью), показывающий, что это несоответствие затрат может иметь иные последствия, чем просто усложнение сравнения планов.

введите описание изображения здесь

Ответы:


21

Кто-нибудь знает, почему эта задача стоит 0,172434 в первом плане, а 3,01702 во втором?

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

Существует еще один способ калькуляции, Smart Seek Costing , о котором мало что известно. Мое предположение (и это все, что есть на данном этапе) заключается в том, что SSC пытается оценить стоимость ввода-вывода с внутренней стороны более подробно, возможно, с учетом локального упорядочения и / или диапазона значений для выборки. Кто знает.

Например, первая операция поиска вводит не только запрошенную строку, но и все строки на этой странице (в порядке индекса). Учитывая общую схему доступа, выборка 1000 строк за 1000 запросов требует только 2 физических чтений, даже с отключенным опережающим чтением и предварительной выборкой. С этой точки зрения стоимость ввода-вывода по умолчанию представляет собой значительную переоценку, а скорректированные с помощью SSC затраты ближе к реальности.

Представляется разумным ожидать, что SSC будет наиболее эффективным, когда цикл управляет поиском индекса более или менее напрямую, а внешняя ссылка соединения является основой операции поиска. Из того, что я могу сказать, SSC всегда пытается выполнить подходящие физические операции, но чаще всего не производит корректировки вниз, когда поиск отделен от объединения другими операциями. Простые фильтры являются одним из исключений, возможно, потому что SQL Server часто может вставить их в оператор доступа к данным. В любом случае, оптимизатор имеет довольно глубокую поддержку выбора.

К сожалению, вычислительный скаляр для внешних проекций подзапроса, похоже, мешает работе SSC. Вычислительные скаляры обычно перемещаются над объединением, но они должны оставаться там, где они есть. Несмотря на это, большинство обычных вычислимых скаляров довольно прозрачны для оптимизации, поэтому это немного удивительно.

Независимо от того, когда физическая операция PhyOp_Rangeпроизводится из простого выбора индекса SelIdxToRng, SSC является эффективным. Когда используется более сложный SelToIdxStrategy(выбор по таблице для стратегии индекса), в результате PhyOp_Rangeзапускается SSC, но результат не уменьшается. Опять же, кажется, что более простые, более прямые операции лучше всего работают с SSC.

Я бы хотел рассказать вам точно, что делает SSC, и показать точные расчеты, но я не знаю этих деталей. Если вы хотите изучить ограниченный вывод трассировки, доступный для себя, вы можете использовать недокументированный флаг трассировки 2398. Пример вывода:

Стоимость интеллектуального поиска (7.1) :: 1.34078e + 154, 0.001

Этот пример относится к группе 7 памятки, альтернативе 1, показывающей верхнюю границу стоимости и коэффициент 0,001. Чтобы увидеть более четкие факторы, обязательно перестраивайте таблицы без параллелизма, чтобы страницы были максимально плотными. Без этого коэффициент больше похож на 0,000821 для вашего примера целевой таблицы. Там, конечно, есть довольно очевидные отношения.

SSC также можно отключить с помощью недокументированного флага трассировки 2399. Когда этот флаг активен, обе стоимости имеют более высокое значение.


8

Не уверен, что это ответ, но это немного долго для комментария. Причиной разницы является чистая спекуляция с моей стороны, и, возможно, она может послужить пищей для размышлений другим.

Упрощенные запросы с планами выполнения.

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

введите описание изображения здесь

Основное различие между этими эквивалентными запросами, которые действительно могут привести к одинаковым планам выполнения, заключается в вычислении скалярного оператора. Я не знаю, почему это должно быть там, но я предполагаю, что это настолько, насколько оптимизатор может пойти, чтобы оптимизировать удаленную таблицу.

Я предполагаю, что наличие скаляра вычислений - это то, что портит стоимость ввода-вывода для второго запроса.

Из Внутри Optimizer: План Costing

Стоимость ЦП рассчитывается как 0,0001581 для первой строки и 0,000011 для последующих строк.
...
Стоимость ввода-вывода 0,003125 в точности равна 1/320, что отражает предположение модели о том, что дисковая подсистема может выполнять 320 операций случайного ввода-вывода в секунду
...
компонент оценки затрат достаточно умен, чтобы признать, что общее число Количество страниц, которые необходимо перенести с диска, никогда не может превышать количество страниц, необходимое для хранения всей таблицы.

В моем случае таблица занимает 5618 страниц, и для получения 1000 строк из 1000000 строк предполагаемое количество необходимых страниц составляет 5,618, что дает стоимость ввода-вывода 0,015625.

Стоимость ЦП для обоих запросов будет одинаковой 0.0001581 * 1000 executions = 0.1581.

Таким образом, согласно статье, указанной выше, мы можем рассчитать стоимость первого запроса, равную 0,173725.

И если предположить, что я прав в том, как вычислительный скаляр вносит беспорядок в стоимость ввода-вывода, его можно рассчитать до 3,2831.

Не совсем то, что показано в планах, но это прямо по соседству.


6

(Это было бы лучше в качестве комментария к ответу Пола, но мне пока не хватает представителя.)

Я хотел предоставить список флагов трассировки (и пару DBCCоператоров), которые я использовал, чтобы прийти к близкому заключению, на случай, если будет полезно исследовать подобные расхождения в будущем. Все это не должно использоваться на производстве .

Сначала я взглянул на Заключительную записку, чтобы увидеть, какие физические операторы используются. Они, конечно, выглядят одинаково в соответствии с графическими планами выполнения. Итак, я использовал флаги трассировки, 3604и 8615первая направляет вывод клиенту, а вторая показывает заключительную заметку:

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

Возвращаясь от Root Group, я нашел эти почти идентичные PhyOp_Rangeоператоры:

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

Единственным очевидным отличием для меня были 2.0и 3.0, которые относятся к их соответствующим «блокнотам 2, оригинал» и «блокнотам 3, оригинал». Проверяя памятку, они ссылаются на одно и то же - поэтому никаких различий пока не выявлено.

Во-вторых, я изучил целый беспорядок флагов трассировки, которые оказались бесполезными для меня - но имеют некоторое интересное содержание. Больше всего я снял с Бенджамина Невареса . Я искал подсказки относительно правил оптимизации, которые применялись в одном случае, а не в другом.

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

В-третьих, я посмотрел, какие правила были применены к нашим, PhyOp_Rangeкоторые выглядят так похоже. Я использовал пару флагов трассировки, упомянутых Полом в сообщении в блоге .

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

На выходе мы видим , что direct- JOINприменял это правило , чтобы получить наш PhyOp_Rangeоператор: Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2). Подвыборка применяла это правило , а не: Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2). Здесь также отображается информация о стоимости интеллектуального поиска, связанная с каждым правилом. Для direct- JOINэто выход (для меня): Smart seek costing (7.2) :: 1.34078e+154 , 0.001. Для подвыборки, это выход: Smart seek costing (9.2) :: 1.34078e+154 , 1.

В конце концов, я не мог сделать много выводов - но ответ Пола закрывает большую часть разрыва. Я хотел бы видеть больше информации о стоимости интеллектуального поиска.


4

На самом деле это тоже не ответ - как отметил Микаэль, эту проблему трудно обсуждать в комментариях ...

Интересно, что если вы конвертируете подзапрос (select KeyCol FROM Target)во встроенный TVF, вы увидите, что план и его стоимость такие же, как и у простого исходного запроса:

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

Планы запроса ( вставьте ссылку на план ):

введите описание изображения здесь

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

Взять, к примеру, следующее:

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

Как бы вы это стоили? Оптимизатор запросов выбирает план, очень похожий на вариант «подзапроса» выше, содержащий скаляр вычислений ( ссылка pastetheplan.com ):

введите описание изображения здесь

Вычислительный скаляр имеет весьма отличную стоимость от варианта «подзапроса», показанного выше, однако это все еще просто предположение, поскольку оптимизатор запросов не может априори узнать, каким может быть количество возвращаемых строк. План использует совпадение хеш-функции для левого внешнего соединения, поскольку оценки строк непознаваемы, и поэтому для них установлено количество строк в целевой таблице.

введите описание изображения здесь

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.