Чому таблиця темпів є більш ефективним рішенням проблеми на Хеллоуїн, ніж жадібна котушка?


14

Розглянемо наступний запит, який вставляє рядки з вихідної таблиці лише у тому випадку, якщо вони ще не є цільовою таблицею:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

Одна з можливих форм плану включає з'єднання злиття і нетерплячий котушок. Для вирішення проблеми Хеллоуїна присутній охочий золотник :

перший план

На моїй машині вищезгаданий код виконує приблизно 6900 мс. Код Repro для створення таблиць міститься внизу питання. Якщо я незадоволений продуктивністю, я можу спробувати завантажити рядки, які потрібно вставити в таблицю темпів, замість того, щоб покладатися на нетерплячу котушку. Ось одна можлива реалізація:

DROP TABLE IF EXISTS #CONSULTANT_RECOMMENDED_TEMP_TABLE;
CREATE TABLE #CONSULTANT_RECOMMENDED_TEMP_TABLE (
    ID BIGINT,
    PRIMARY KEY (ID)
);

INSERT INTO #CONSULTANT_RECOMMENDED_TEMP_TABLE WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1);

Новий код виконує приблизно 4400 мс. Я можу отримати фактичні плани та використовувати фактичну статистику часу ™, щоб вивчити, де проводиться час на рівні оператора. Зауважте, що запит на фактичний план додає значні накладні витрати для цих запитів, тому загальна сума не буде відповідати попереднім результатам.

╔═════════════╦═════════════╦══════════════╗
  operator    first query  second query 
╠═════════════╬═════════════╬══════════════╣
 big scan     1771         1744         
 little scan  163          166          
 sort         531          530          
 merge join   709          669          
 spool        3202         N/A          
 temp insert  N/A          422          
 temp scan    N/A          187          
 insert       3122         1545         
╚═════════════╩═════════════╩══════════════╝

План запитів із нетерплячою котушкою, здається, витрачає значно більше часу на оператори вставки та котушки порівняно з планом, який використовує таблицю темп.

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

Я на SQL Server 2017 CU 11, якщо хтось хоче знати. Ось код для заповнення таблиць, використаних у вищезазначених запитах:

DROP TABLE IF EXISTS dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR;

CREATE TABLE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR (
ID BIGINT NOT NULL,
PRIMARY KEY (ID)
);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (20000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.A_HEAP_OF_MOSTLY_NEW_ROWS;

CREATE TABLE dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (
ID BIGINT NOT NULL
);

INSERT INTO dbo.A_HEAP_OF_MOSTLY_NEW_ROWS WITH (TABLOCK)
SELECT TOP (1900000) 19999999 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Відповіді:


14

Це те, що я називаю Ручним захистом на Хеллоуїн .

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

Чому план із таблицею темпів більш ефективний? Чи все-таки не бажає котушка здебільшого просто внутрішня таблиця темпів?

Шпулька має деякі характеристики тимчасової таблиці, але дві не є точними еквівалентами. Зокрема, котушка, по суті, є порядковою впорядкованою вставкою до структури b-дерева . Це користь від оптимізації блокування та ведення журналів, але не підтримує оптимізацію великого навантаження .

Отже, часто можна досягти кращої продуктивності, розбивши запит природним чином: Масове завантаження нових рядків у тимчасову таблицю чи змінну, а потім виконання оптимізованої вставки (без явного захисту на Хеллоуїн) з тимчасового об’єкта.

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

В якості побічної записки цікаво подумати про те, як можна вирішити проблему на Хеллоуїн, використовуючи рядкові версії. Можливо, майбутня версія SQL Server надасть цю функцію за відповідних обставин.


Як згадував Майкл Куц у коментарі, ви також можете вивчити можливість використання оптимізації заповнення отворів, щоб уникнути явного HP. Один із способів досягти цього для демонстрації - створити унікальний індекс (кластеризований, якщо хочете) на IDстовпці A_HEAP_OF_MOSTLY_NEW_ROWS.

CREATE UNIQUE INDEX i ON dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (ID);

За допомогою цієї гарантії оптимізатор може використовувати заповнення отворів та набір рядків:

MERGE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (SERIALIZABLE) AS HICETY
USING dbo.A_HEAP_OF_MOSTLY_NEW_ROWS AS AHOMNR
    ON AHOMNR.ID = HICETY.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID) VALUES (AHOMNR.ID);

План МЕРГ

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


5

Щоб трохи розширити відповідь Поля, частина різниці у минулому часі між котушкою та підходами до таблиці темп, схоже, зводиться до недостатньої підтримки DML Request Sort варіанту в плані котушки. З бездокументованим прапором слідів 8795 минулий час підходу до таблиці темп підскакує з 4400 мс до 5600 мс.

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1, QUERYTRACEON 8795);

Зверніть увагу, що це не зовсім еквівалентно вставці, виконаній планом котушки. Цей запит записує значно більше даних у журнал транзакцій.

Такий самий ефект можна побачити і в зворотному напрямку з деякою хитрістю. Можна закликати SQL Server використовувати сорт замість котушки для захисту від Хеллоуїна. Одна реалізація:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (987654321) 
maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
ORDER BY maybe_new_rows.ID, maybe_new_rows.ID + 1
OPTION (MAXDOP 1, QUERYTRACEON 7470, MERGE JOIN);

Тепер у плані замість котушки є оператор сортування TOP N. Різновидом є оператор блокування, тому котушка більше не потрібна:

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

Що ще важливіше, зараз у нас є підтримка DML Request Sortваріанту. Переглянувши знову фактичну статистику часу, оператор вставки зараз займає лише 1623 мс. Весь план займає близько 5400 мс для виконання, не вимагаючи фактичного плану.

Як пояснює Гюго , оператор Eager Spool зберігає порядок. Це найпростіше побачити з TOP PERCENTпланом. Прикро, що оригінальний запит із котушкою не може краще скористатися відсортованим характером даних у котушці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.