Розглянемо наступний запит, який вставляє рядки з вихідної таблиці лише у тому випадку, якщо вони ще не є цільовою таблицею:
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;