Betrachten Sie die folgende Abfrage, mit der Zeilen aus einer Quellentabelle nur dann eingefügt werden, wenn sie nicht bereits in der Zieltabelle enthalten sind:
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);
Eine mögliche Planform umfasst eine Zusammenführungsverknüpfung und eine eifrige Spule. Der eifrige Spulenoperator ist anwesend, um das Halloween-Problem zu lösen :
Auf meinem Computer wird der obige Code in ca. 6900 ms ausgeführt. Der Repro-Code zum Erstellen der Tabellen befindet sich am Ende der Frage. Wenn ich mit der Leistung unzufrieden bin, kann ich versuchen, die einzufügenden Zeilen in eine temporäre Tabelle zu laden, anstatt mich auf die eifrige Spool zu verlassen. Hier ist eine mögliche Implementierung:
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);
Der neue Code wird in ca. 4400 ms ausgeführt. Ich kann aktuelle Pläne abrufen und mithilfe von Actual Time Statistics ™ prüfen, wo auf Bedienerebene Zeit verbracht wird. Beachten Sie, dass das Anfordern eines tatsächlichen Plans zu einem erheblichen Mehraufwand für diese Abfragen führt, sodass die Gesamtsummen nicht mit den vorherigen Ergebnissen übereinstimmen.
╔═════════════╦═════════════╦══════════════╗
║ 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 ║
╚═════════════╩═════════════╩══════════════╝
Der Abfrageplan mit dem eifrigen Spool scheint erheblich mehr Zeit für die Einfüge- und Spool-Operatoren aufzuwenden als der Plan, der die temporäre Tabelle verwendet.
Warum ist der Plan mit der temporären Tabelle effizienter? Ist eine eifrige Spule nicht sowieso meist nur eine interne temporäre Tabelle? Ich glaube, ich suche nach Antworten, die sich auf Interna konzentrieren. Ich kann sehen, wie unterschiedlich die Call-Stacks sind, kann aber das große Ganze nicht verstehen.
Ich bin auf SQL Server 2017 CU 11 für den Fall, dass jemand wissen möchte. Hier ist Code zum Auffüllen der in den obigen Abfragen verwendeten Tabellen:
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;