Warum ist die zweite INSERT
Aussage ~ 5x langsamer als die erste?
Aufgrund der Menge der generierten Protokolldaten denke ich, dass die zweite nicht für eine minimale Protokollierung geeignet ist. Die Dokumentation im Leistungshandbuch zum Laden von Daten zeigt jedoch, dass beide Einfügungen minimal protokolliert werden können sollten. Wenn also die minimale Protokollierung der Hauptleistungsunterschied ist, warum qualifiziert sich die zweite Abfrage nicht für die minimale Protokollierung? Was kann getan werden, um die Situation zu verbessern?
Abfrage Nr. 1: Einfügen von 5-MM-Zeilen mit INSERT ... WITH (TABLOCK)
Betrachten Sie die folgende Abfrage, bei der 5-MM-Zeilen in einen Heap eingefügt werden. Diese Abfrage wird ausgeführt 1 second
und generiert 64MB
Transaktionsprotokolldaten, wie von gemeldet sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Abfrage 2: Einfügen derselben Daten, aber SQL unterschätzt die Anzahl der Zeilen
Betrachten Sie nun diese sehr ähnliche Abfrage, die mit genau denselben Daten arbeitet, aber zufällig aus einer Tabelle (oder einer komplexen SELECT
Anweisung mit vielen Verknüpfungen in meinem tatsächlichen Produktionsfall) stammt, in der die Kardinalitätsschätzung zu niedrig ist. Diese Abfrage wird in Transaktionsprotokolldaten ausgeführt 5.5 seconds
und generiert diese 461MB
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Vollständiges Skript
In diesem Pastebin finden Sie einen vollständigen Satz von Skripten zum Generieren der Testdaten und zum Ausführen eines dieser Szenarien. Beachten Sie, dass Sie eine Datenbank verwenden müssen, die sich im SIMPLE
Wiederherstellungsmodell befindet .
Geschäftlicher Zusammenhang
Wir bewegen uns nur selten in Millionen von Datenzeilen, und es ist wichtig, dass diese Vorgänge sowohl hinsichtlich der Ausführungszeit als auch der Festplatten-E / A-Last so effizient wie möglich sind. Wir hatten anfangs den Eindruck, dass das Erstellen und Verwenden einer Heap-Tabelle INSERT...WITH (TABLOCK)
ein guter Weg ist, dies zu tun, sind aber jetzt weniger zuversichtlich geworden, da wir die oben gezeigte Situation in einem tatsächlichen Produktionsszenario beobachtet haben (wenn auch mit komplexeren Abfragen, nicht mit der vereinfachte Version hier).
SELECT
Anweisung mit zahlreichen Verknüpfungen, die die Ergebnismenge für die generiertINSERT
. Diese Verknüpfungen führen zu schlechten Kardinalitätsschätzungen für den Operator zum Einfügen der endgültigen Tabelle (den ich im Repro-Skript über den fehlerhaftenUPDATE STATISTICS
Aufruf simuliert habe ), und es ist daher nicht ganz so einfach, einenUPDATE STATISTICS
Befehl zur Behebung des Problems auszugeben . Ich stimme voll und ganz zu, dass die Vereinfachung der Abfrage, damit der Kardinalitätsschätzer sie leichter verstehen kann, ein guter Ansatz ist, aber es ist kein Trival, einen Ansatz zu implementieren, wenn eine komplexe Geschäftslogik gegeben ist.