it-swarm.com.de

Warum ist ein temporärer Tisch eine effizientere Lösung für das Halloween-Problem als eine eifrige Spule?

Betrachten Sie die folgende Abfrage, mit der Zeilen aus einer Quelltabelle nur 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ührungsverbindung und eine eifrige Spule. Der eifrige Spool-Operator ist anwesend, um das Halloween-Problem zu lösen:

first plan

Auf meinem Computer wird der obige Code in ungefähr 6900 ms ausgeführt. Repro-Code zum Erstellen der Tabellen ist am Ende der Frage enthalten. Wenn ich mit der Leistung unzufrieden bin, könnte ich versuchen, die Zeilen, die in eine temporäre Tabelle eingefügt werden sollen, 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 der tatsächlichen Zeitstatistik ™ untersuchen, wo auf Bedienerebene Zeit verbracht wird. Beachten Sie, dass das Anfordern eines tatsächlichen Plans einen erheblichen Aufwand für diese Abfragen verursacht, 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 wesentlich mehr Zeit für die Einfüge- und Spool-Operatoren zu verwenden 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 meistens nur eine interne temporäre Tabelle? Ich glaube, ich suche nach Antworten, die sich auf Interna konzentrieren. Ich kann sehen, wie unterschiedlich die Anrufstapel sind, kann aber das große Ganze nicht herausfinden.

Ich bin auf SQL Server 2017 CU 11, falls jemand es 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;
14
Joe Obbish

Dies nenne ich Manueller Halloween-Schutz .

Ein Beispiel für die Verwendung mit einer Update-Anweisung finden Sie in meinem Artikel Optimieren von Update-Abfragen . Man muss ein bisschen vorsichtig sein, um die gleiche Semantik beizubehalten, indem man beispielsweise die Zieltabelle gegen alle gleichzeitigen Änderungen sperrt, während die separaten Abfragen ausgeführt werden, wenn dies in Ihrem Szenario relevant ist.

Warum ist der Plan mit der temporären Tabelle effizienter? Ist eine eifrige Spule nicht sowieso meistens nur eine interne temporäre Tabelle?

Eine Spule hat einige der Merkmale einer temporären Tabelle, aber die beiden sind keine exakten Äquivalente. Insbesondere ist eine Spool im Wesentlichen eine zeilenweise ngeordnete Einfügung in eine B-Baum-Struktur . Es profitiert zwar von Sperr- und Protokollierungsoptimierungen, unterstützt jedoch keine Massenlastoptimierungen .

Infolgedessen kann häufig eine bessere Leistung erzielt werden, indem die Abfrage auf natürliche Weise aufgeteilt wird: Laden Sie die neuen Zeilen in großen Mengen in eine temporäre Tabelle oder Variable und führen Sie dann eine optimierte Einfügung (ohne expliziten Halloween-Schutz) aus dem temporären Objekt durch.

Durch diese Trennung haben Sie außerdem zusätzliche Freiheit, die Lese- und Schreibteile der ursprünglichen Anweisung separat abzustimmen.

Als Randnotiz ist es interessant darüber nachzudenken, wie das Halloween-Problem mithilfe von Zeilenversionen angegangen werden könnte. Möglicherweise bietet eine zukünftige Version von SQL Server diese Funktion unter geeigneten Umständen.


Wie Michael Kutz in einem Kommentar angedeutet hat, könnten Sie auch die Möglichkeit prüfen, die Lochfüllungsoptimierung zu nutzen, um explizite HP zu vermeiden. Eine Möglichkeit, dies für die Demo zu erreichen, besteht darin, einen eindeutigen Index (gruppiert, wenn Sie möchten) für die Spalte ID von A_HEAP_OF_MOSTLY_NEW_ROWS Zu erstellen.

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

Mit dieser Garantie kann der Optimierer das Füllen von Löchern und das Teilen von Rowsets verwenden:

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);

(MERGE plan

Obwohl dies interessant ist, können Sie in vielen Fällen dennoch eine bessere Leistung erzielen, indem Sie den sorgfältig implementierten manuellen Halloween-Schutz einsetzen.

14
Paul White 9

Um die Antwort von Paul ein wenig zu erweitern, scheint ein Teil des Unterschieds in der verstrichenen Zeit zwischen den Ansätzen der Spule und der temporären Tabelle auf die mangelnde Unterstützung für die DML Request Sort Option im Spool-Plan. Mit dem undokumentierten Ablaufverfolgungsflag 8795 springt die verstrichene Zeit für den temporären Tabellenansatz von 4400 ms auf 5600 ms.

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);

Beachten Sie, dass dies nicht genau der Einfügung entspricht, die vom Spool-Plan ausgeführt wird. Diese Abfrage schreibt wesentlich mehr Daten in das Transaktionsprotokoll.

Der gleiche Effekt kann mit einigen Tricks umgekehrt gesehen werden. Es ist möglich, SQL Server zu ermutigen, für Halloween Protection eine Sortierung anstelle einer Spool zu verwenden. Eine Implementierung:

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);

Jetzt hat der Plan einen TOP N-Sortieroperator anstelle der Spule. Die Sortierung ist ein Blockierungsoperator, sodass die Spule nicht mehr benötigt wird:

(enter image description here

Noch wichtiger ist, wir haben jetzt Unterstützung für die DML Request Sort Möglichkeit. Bei erneuter Betrachtung der tatsächlichen Zeitstatistik benötigt der Einfügeoperator nur noch 1623 ms. Die Ausführung des gesamten Plans dauert ca. 5400 ms, ohne dass ein tatsächlicher Plan angefordert wird.

Wie Hugo erklärt , behält der Eager Spool-Operator die Ordnung bei. Das kann am einfachsten mit einem TOP PERCENT planen. Es ist bedauerlich, dass die ursprüngliche Abfrage mit der Spool die sortierte Natur der Daten in der Spool nicht besser ausnutzen kann.

5
Joe Obbish