Ich bin mir ziemlich sicher, dass die Tabellendefinitionen in der Nähe davon liegen:
CREATE TABLE dbo.households
(
tempId integer NOT NULL,
n integer NOT NULL,
HHID integer IDENTITY NOT NULL,
CONSTRAINT [UQ dbo.households HHID]
UNIQUE NONCLUSTERED (HHID),
CONSTRAINT [PK dbo.households tempId, n]
PRIMARY KEY CLUSTERED (tempId, n)
);
CREATE TABLE dbo.persons
(
tempId integer NOT NULL,
sporder integer NOT NULL,
n integer NOT NULL,
PERID integer IDENTITY NOT NULL,
HHID integer NOT NULL,
CONSTRAINT [UQ dbo.persons HHID]
UNIQUE NONCLUSTERED (PERID),
CONSTRAINT [PK dbo.persons tempId, n, sporder]
PRIMARY KEY CLUSTERED (tempId, n, sporder)
);
Ich habe keine Statistiken für diese Tabellen oder Ihre Daten, aber das Folgende wird zumindest die Kardinalität der Tabelle korrekt einstellen (die Seitenzahlen sind eine Vermutung):
UPDATE STATISTICS dbo.persons
WITH
ROWCOUNT = 5239842,
PAGECOUNT = 100000;
UPDATE STATISTICS dbo.households
WITH
ROWCOUNT = 1928783,
PAGECOUNT = 25000;
Abfrageplananalyse
Die Abfrage, die Sie jetzt haben, lautet:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n;
Dies erzeugt den ziemlich ineffizienten Plan:
Die Hauptprobleme in diesem Plan sind das Hash-Join und Sortieren. Für beide ist eine Speicherzuweisung erforderlich (der Hash-Join muss eine Hash-Tabelle erstellen, und die Sortierung benötigt Platz zum Speichern der Zeilen, während die Sortierung fortschreitet). Der Plan-Explorer zeigt, dass dieser Abfrage 765 MB gewährt wurden:
Dies ist ziemlich viel Serverspeicher für eine Abfrage! Genauer gesagt wird diese Speicherzuweisung vor Beginn der Ausführung basierend auf Zeilenanzahl und Größenschätzungen festgelegt.
Wenn sich herausstellt, dass der Speicher zur Ausführungszeit nicht ausreicht, werden zumindest einige Daten für den Hash und / oder die Sortierung auf die physische Tempdb- Festplatte geschrieben. Dies wird als "Verschütten" bezeichnet und kann ein sehr langsamer Vorgang sein. Sie können diese Verschüttungen (in SQL Server 2008) mithilfe der Hash-Warnungen und Sortierwarnungen der Profiler-Ereignisse verfolgen .
Die Schätzung für die Build-Eingabe der Hash-Tabelle ist sehr gut:
Die Schätzung für die Sortiereingabe ist weniger genau:
Sie müssten Profiler verwenden, um dies zu überprüfen, aber ich vermute, dass die Sortierung in diesem Fall auf Tempdb übertragen wird. Es ist auch möglich, dass die Hash-Tabelle ebenfalls verschüttet wird, aber das ist weniger eindeutig.
Beachten Sie, dass der für diese Abfrage reservierte Speicher zwischen der Hash-Tabelle und der Sortierung aufgeteilt wird, da sie gleichzeitig ausgeführt werden. Die Plan-Eigenschaft "Speicherfraktionen" zeigt den relativen Betrag der Speicherzuweisung an, die voraussichtlich von jeder Operation verwendet wird.
Warum Sortieren und Hash?
Die Sortierung wird vom Abfrageoptimierer eingeführt, um sicherzustellen, dass Zeilen in der Reihenfolge der Clusterschlüssel beim Operator "Clustered Index Update" ankommen. Dies fördert den sequentiellen Zugriff auf die Tabelle, was häufig viel effizienter ist als der Direktzugriff.
Der Hash-Join ist eine weniger offensichtliche Wahl, da seine Eingaben ähnliche Größen haben (jedenfalls in erster Näherung). Hash-Join ist am besten geeignet, wenn eine Eingabe (diejenige, die die Hash-Tabelle erstellt) relativ klein ist.
In diesem Fall bestimmt das Kalkulationsmodell des Optimierers, dass der Hash-Join die billigere der drei Optionen ist (Hash, Merge, verschachtelte Schleifen).
Verbessernde Leistung
Das Kostenmodell macht es nicht immer richtig. Die Kosten für die parallele Zusammenführung werden tendenziell überschätzt, insbesondere wenn die Anzahl der Threads zunimmt. Wir können einen Merge-Join mit einem Abfragehinweis erzwingen:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (MERGE JOIN);
Dies erzeugt einen Plan, der nicht so viel Speicher benötigt (da für den Merge-Join keine Hash-Tabelle erforderlich ist):
Die problematische Sortierung ist immer noch vorhanden, da beim Zusammenführen von Joins nur die Reihenfolge der Join-Schlüssel (tempId, n) beibehalten wird, die Cluster-Schlüssel jedoch (tempId, n, sporder). Möglicherweise ist der Merge-Join-Plan nicht besser als der Hash-Join-Plan.
Verschachtelte Schleifen verbinden
Wir können auch versuchen, eine verschachtelte Schleife zu verbinden:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (LOOP JOIN);
Der Plan für diese Abfrage lautet:
Dieser Abfrageplan wird vom Kalkulationsmodell des Optimierers als der schlechteste angesehen, weist jedoch einige sehr wünschenswerte Funktionen auf. Erstens erfordert der Join verschachtelter Schleifen keine Speicherzuweisung. Zweitens kann die Schlüsselreihenfolge aus der Persons
Tabelle beibehalten werden, sodass keine explizite Sortierung erforderlich ist. Möglicherweise ist dieser Plan relativ gut, vielleicht sogar gut genug.
Parallele verschachtelte Schleifen
Der große Nachteil des Plans für verschachtelte Schleifen besteht darin, dass er auf einem einzelnen Thread ausgeführt wird. Es ist wahrscheinlich, dass diese Abfrage von Parallelität profitiert, aber der Optimierer entscheidet, dass dies hier keinen Vorteil hat. Dies ist auch nicht unbedingt richtig. Leider gibt es keinen integrierten Abfragehinweis, um einen parallelen Plan zu erhalten, aber es gibt einen undokumentierten Weg:
UPDATE t1
SET t1.HHID = t2.HHID
FROM dbo.persons AS t1
INNER JOIN dbo.households AS t2
ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);
Das Aktivieren des Ablaufverfolgungsflags 8649 mit dem QUERYTRACEON
Hinweis erzeugt diesen Plan:
Jetzt haben wir einen Plan, der die Sortierung vermeidet, keinen zusätzlichen Speicher für den Join benötigt und Parallelität effektiv nutzt. Sie sollten feststellen, dass diese Abfrage viel besser funktioniert als die Alternativen.
Weitere Informationen zur Parallelität finden Sie in meinem Artikel Erzwingen eines Ausführungsplans für parallele Abfragen :