Ein Beispiel aus dieser Frage zeigt, dass SQL Server einen vollständigen Index-Scan auswählt, um eine Abfrage wie die folgende zu lösen:
select distinct [typeName] from [types]
Wobei [typeName] einen nicht gruppierten, nicht eindeutigen aufsteigenden Index enthält. Sein Beispiel hat 200 Millionen Zeilen, aber nur 76 eindeutige Werte. Es scheint, als wäre ein Suchplan mit dieser Dichte die bessere Wahl (~ 76 mehrfache binäre Suchen)?
Sein Fall könnte normalisiert werden, aber der Grund für die Frage ist, dass ich wirklich so etwas lösen möchte:
select TransactionId, max(CreatedUtc)
from TxLog
group by TransactionId
Es gibt einen Index für (TransactionId, MaxCreatedUtc)
.
Das Umschreiben mit einer normalisierten Quelle (dt) ändert nichts am Plan.
select dt.TransactionId, MaxCreatedUtc
from [Transaction] dt -- distinct transactions
cross apply
(
select Max(CreatedUtc) as MaxCreatedUtc
from TxLog tl
where tl.TransactionId = dt.TransactionId
) ca
Wenn Sie nur die CA-Unterabfrage als skalare UDF ausführen, wird ein Plan mit 1 Suche angezeigt.
select max(CreatedUtc) as MaxCreatedUtc
from Pub.TransactionLog
where TransactionID = @TxId;
Die Verwendung dieser skalaren UDF in der ursprünglichen Abfrage scheint zu funktionieren, verliert jedoch die Parallelität (bekanntes Problem mit UDFs):
select t.typeName,
Pub.ufn_TransactionMaxCreatedUtc(t.TransactionId) as MaxCreatedUtc
from Pub.[Transaction] t
Beim Umschreiben mit einem Inline-TVF wird es wieder auf den Scan-basierten Plan zurückgesetzt.
Aus Antwort / Kommentar @ypercube:
select TransactionId, MaxCreatedUtc
from Pub.[Transaction] t
cross apply
(
select top (1) CreatedUtc as MaxCreatedUtc
from Pub.TransactionLog l
where l.TransactionID = t.TransactionId
order by CreatedUtc desc
) ca
Plan sieht gut aus. Keine Parallelität, aber sinnlos da so schnell. Muss dies irgendwann bei einem größeren Problem versuchen. Vielen Dank.
quelle
Antworten:
Ich habe genau das gleiche Setup und habe die gleichen Phasen des Umschreibens der Abfrage durchlaufen.
In meinem Fall sind die Tabellennamen und die Bedeutung etwas unterschiedlich, aber die Gesamtstruktur ist dieselbe. Ihre Tabelle
Transactions
entspricht meiner TabellePortalElevators
unten. Es hat ~ 2000 Zeilen. Ihr TischTxLog
entspricht meinem TischPlaybackStats
. Es hat ~ 150 Millionen Zeilen. Es hat Index auf(ElevatorID, DataSourceRowID)
, genau wie Sie.Ich werde verschiedene Varianten der Abfrage für die realen Daten ausführen und Ausführungspläne, E / A und Zeitstatistiken vergleichen. Ich verwende SQL Server 2008 Standard.
GROUP BY mit MAX
Das gleiche wie für Sie optimiert den Index und aggregiert die Ergebnisse. Langsam.
Einzelne Reihe
Mal sehen, was der Optimierer tun würde, wenn ich
MAX
nur eine Zeile anfordern würde :Das Optimierungsprogramm ist intelligent genug, um den Index zu verwenden, und es führt eine Suche durch. Übrigens können wir sehen, dass der Optimierer den
TOP
Operator verwendet, obwohl die Abfrage ihn nicht hat. Dies ist ein beredtes Zeichen dafür , dass OptimierungswegMAX
undTOP
etwas gemeinsam im Motor haben, aber sie sind verschieden , wie wir weiter unten sehen werden.KREUZ MIT MAX
Das Optimierungsprogramm scannt weiterhin den Index. Es ist nicht klug genug, hier
MAX
inTOP
Suchanfragen zu konvertieren und diese zu scannen. Langsam. Ich habe ursprünglich nicht an diese Variante gedacht, mein nächster Versuch war skalares UDF.Skalare UDF
Ich habe gesehen, dass dieser Plan für das Abrufen
MAX
einer einzelnen Zeile eine Indexsuche hatte, also habe ich diese einfache Abfrage in eine skalare UDF eingefügt.Es läuft schnell. Zumindest viel schneller als
Group by
. Leider zeigt der Ausführungsplan keine Details zu UDF und was noch schlimmer ist, er zeigt nicht die tatsächlichen E / A-Statistiken (er enthält keine von UDF generierten E / A). Sie müssen Profiler ausführen, um alle Aufrufe der Funktion und ihre Statistiken anzuzeigen. Dieser Plan zeigt nur 6 Lesevorgänge. Der Plan für die einzelne Zeile enthält 4 Lesevorgänge, sodass die tatsächliche Zahl in der Nähe von: liegt6 + 2779 * 4 = 6 + 11,116 = 11,122
.CROSS APPLY mit TOP
Schließlich entdeckte ich das
CROSS APPLY
und wie es angewendet werden kann ;-) in diesem Fall.Hier ist der Optimierer klug genug, um ~ 2000 Suchvorgänge durchzuführen. Sie können sehen, dass die Anzahl der Lesevorgänge viel geringer ist als für
group by
. Schnell.Interessanterweise ist die Anzahl der Lesevorgänge hier (11.850) etwas höher als die mit UDF (11.122) geschätzten Lesevorgänge. Tabellen-E / A-Statistiken mit
CROSS APPLY
11.844 Lesevorgängen und 2.779 Scan-Zählwerten der großen Tabelle geben die11,844 / 2,779 ~= 4.26
Lesevorgänge pro Indexsuche an. Bei der Suche nach einigen Werten werden höchstwahrscheinlich 4 Lesevorgänge und bei einigen 5 mit einem Durchschnitt von 4,26 verwendet. Es gibt 2.779 Suchvorgänge, aber es gibt nur Werte für 2.130 Zeilen. Wie gesagt, es ist schwierig, mit UDF ohne Profiler eine echte Anzahl von Lesevorgängen zu erhalten.Rekursiver CTE
Wie in den Kommentaren erwähnt, beschrieb Paul White eine Methode zum Überspringen des rekursiven Index- Scans, um bestimmte Werte in einer großen Tabelle zu finden, ohne einen vollständigen Index-Scan durchzuführen, aber Index-Suchen rekursiv durchzuführen. Um die Rekursion zu starten, müssen wir den Wert
MIN
oderMAX
für einen Anker finden, und dann addiert jeder Rekursionsschritt nacheinander den nächsten Wert. Der Beitrag erklärt es im Detail.Es ist ziemlich schnell, obwohl es fast doppelt so viele Lesevorgänge ausführt wie
CROSS APPLY
. Es liest 12.781 einWorktable
und 8.524 einPlaybackStats
. Auf der anderen Seite werden so viele Suchvorgänge ausgeführt, wie unterschiedliche Werte in der großen Tabelle vorhanden sind.CROSS APPLY
mitTOP
führt so viele Suchvorgänge durch, wie Zeilen in der kleinen Tabelle vorhanden sind. In meinem Fall hat eine kleine Tabelle 2.779 Zeilen, aber eine große Tabelle hat nur 2.130 verschiedene Werte.Zusammenfassung
Ich habe jede Abfrage dreimal ausgeführt und die beste Zeit ausgewählt. Es gab keine physischen Lesungen.
Fazit
In diesem speziellen
greatest-n-per-group
Problemfall haben wir:n=1
;;Zwei beste Methoden sind:
Falls wir eine kleine Tabelle mit der Liste der Gruppen haben, ist die beste Methode
CROSS APPLY
mitTOP
.Wenn wir nur eine große Tabelle haben, ist die beste Methode
Recursive Index Skip Scan
.quelle