Чому орієнтовна вартість (однакових) 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 рядків обидва вищезазначені все ще мають однакову форму плану з вкладеними петлями та планом, без того, щоб похідна таблиця виявилася дешевшою, але для таблиці 10000 рядків послідовності та тієї ж цільової таблиці, що вище, ніж різниця у витратах, змінює план форма (з повним скануванням та об'єднанням об'єднань, здається порівняно привабливішою, ніж прагне дорого коштувати), що показує, що ця невідповідність витрат може мати інші наслідки, ніж просто ускладнювати порівняння планів.

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

Відповіді:


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;

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

Основна відмінність цих еквівалентних запитів, які дійсно можуть призвести до однакових планів виконання, - це обчислювальний скалярний оператор. Я не знаю, чому це повинно бути там, але я думаю, що настільки оптимізатор може піти на оптимізацію отриманої таблиці.

Я здогадуюсь, що наявність обчислювального скаляра - це те, що зменшує вартість IO для другого запиту.

З Всередині Optimizer: План Costing

Вартість CPU обчислюється як 0,0001581 для першого ряду, і 0,000011 для наступних рядків.
...
Вартість вводу / виводу 0,003125 рівно 1/320 - відображаючи припущення моделі, що дискова підсистема може виконувати 320 випадкових операцій вводу / виводу за секунду
...
компонент калькуляції досить розумний, щоб визнати, що загальна кількість сторінки, які потрібно принести з диска, ніколи не можуть перевищувати кількість сторінок, необхідних для зберігання всієї таблиці.

У моєму випадку таблиця займає 5618 сторінок, а для отримання 1000 рядків із 1000000 рядків орієнтовна кількість необхідних сторінок - 5 618, що забезпечує вартість IO 0,015625.

Вартість процесора для обох швів запитів однакова 0.0001581 * 1000 executions = 0.1581,.

Отже, відповідно до статті, зв'язаної вище, ми можемо розрахувати вартість першого запиту 0,173725.

І якщо припустити, що я правильний щодо того, як обчислювальний скаляр створює безлад у вартості IO, його можна обчислити до 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_Ranges, які виглядають так схоже. Я використав пару прапорів слідів, згаданих Полом у своїй публікації в блозі .

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.