Rozważ następujące zapytanie, które wstawia wiersze z tabeli źródłowej tylko wtedy, gdy nie ma ich już w tabeli docelowej:
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);
Jeden z możliwych kształtów obejmuje połączenie scalające i chętną szpulę. Chętny operator szpuli jest obecny, aby rozwiązać problem Halloween :
Na moim komputerze powyższy kod działa w około 6900 ms. Kod repro do tworzenia tabel znajduje się na dole pytania. Jeśli jestem niezadowolony z wydajności, mogę spróbować załadować wiersze do wstawienia do tabeli tymczasowej zamiast polegać na chętnej szpuli. Oto jedna możliwa implementacja:
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);
Nowy kod wykonuje się w około 4400 ms. Potrafię uzyskać aktualne plany i skorzystać z Actual Time Statistics ™, aby sprawdzić, gdzie spędzany jest czas na poziomie operatora. Pamiętaj, że zapytanie o rzeczywisty plan powoduje znaczne obciążenie tych zapytań, więc sumy nie będą pasować do poprzednich wyników.
╔═════════════╦═════════════╦══════════════╗
║ 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 ║
╚═════════════╩═════════════╩══════════════╝
Plan zapytań z chętnym buforem wydaje się poświęcać znacznie więcej czasu operatorom wstawiania i buforowania w porównaniu do planu korzystającego z tabeli tymczasowej.
Dlaczego plan z tabelą temp jest bardziej wydajny? Czy i tak chętna szpula nie jest głównie tylko wewnętrznym stołem tymczasowym? Myślę, że szukam odpowiedzi, które koncentrują się na wewnętrznych. Widzę, jak różnią się stosy połączeń, ale nie mogę zrozumieć dużego obrazu.
Jestem na SQL Server 2017 CU 11 na wypadek, gdyby ktoś chciał wiedzieć. Oto kod do wypełnienia tabel używanych w powyższych zapytaniach:
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;