Diese Abfrage wird in ~ 21 Sekunden ausgeführt ( Ausführungsplan ):
select
a.month
, count(*)
from SubqueryTest a
where a.year = (select max(b.year) from SubqueryTest b)
group by a.month
Wenn die Unterabfrage durch eine Variable ersetzt wird, wird sie in <1 Sekunde ausgeführt ( Ausführungsplan ):
declare @year float
select @year = max(b.year) from SubqueryTest b
select
month
, count(*)
from SubqueryTest where year = @year group by month
Nach dem Ausführungsplan zu urteilen, wird die Unterauswahl "select max ..." für jede der Millionen Zeilen in "SubqueryTest a:" ausgeführt, weshalb es so lange dauert.
Meine Frage: Da die Unterauswahl skalar, deterministisch und nicht korreliert ist, warum macht der Abfrageoptimierer nicht das, was ich in meinem zweiten Beispiel getan habe, und führt die Unterabfrage einmal aus, speichert das Ergebnis und verwendet es dann für die Hauptabfrage? Ich bin mir sicher, dass mein Verständnis von SQL Server nur eine Lücke aufweist, aber ich würde wirklich gerne beim Ausfüllen helfen - ein paar Stunden mit Google haben nicht geholfen.
Die Tabelle ist etwas mehr als 1 GB mit fast 28 Millionen Datensätzen:
CREATE TABLE SubqueryTest(
[pk_id] [int] IDENTITY(1,1) NOT NULL
, [Year] [float] NULL
, [Month] [float] NULL PRIMARY KEY CLUSTERED ([pk_id] ASC))
CREATE NONCLUSTERED INDEX idxSubqueryTest ON SubqueryTest ([Year] ASC)
quelle
Year
als Schwimmer haben. Sorry, nein, das macht für Stardates Sinn . AberMonth
als Schwimmer? Wirft mich wirklich auf.Antworten:
Der langsame Plan berechnet nicht die
MAX
für jede Zeile in der äußeren Abfrage.Tatsächlich berechnet es es niemals explizit.
Es gibt einen ähnlichen Plan wie
Langsamer Plan (geschätzte Zeilenanzahl)
Sie haben einen nicht abdeckenden Index,
year asc
sodass dieser rückwärts gescannt wird, um die Zeilen im ersten Jahr abzurufen (wird aufgrund des implizitenIS NOT NULL
Prädikats als Suche angezeigt).Leider scheint es nicht zwischen
TOP 1
undTOP 1 WITH TIES
bei der Schätzung der Zeilenanzahl zu unterscheiden .In diesem Fall macht es einen großen Unterschied. (geschätzte 2-Schlüssel-Suche im Vergleich zu tatsächlichen 4.424.803), sodass Sie einen unangemessenen Plan erhalten.
Langsamer Plan (tatsächliche Zeilenanzahl)
Sie können
month
in den Indexyear
entweder als Schlüssel oder als eingeschlossene Spalte hinzufügen , um den Index abzudecken. Der Vorteil des Hinzufügens als Sekundärschlüsselspalte besteht darin, dass es dann ohne zusätzliche Sortierung in ein Stream-Aggregat eingespeist werden kann (obwohl ein Hash-Aggregat für nur 12 verschiedene Werte ohnehin in Ordnung wäre).Ein nicht abdeckender Index für eine solche nicht selektive Spalte ist für die überwiegende Mehrheit der Abfragen wirklich ziemlich nutzlos. Der Index wird vom "schnellen" Plan völlig ignoriert, der einen parallelen Scan der gesamten Tabelle durchführt und das Prädikat für alle 27.445.400 Zeilen auswertet (anstatt die große Anzahl von Suchvorgängen durchzuführen).
quelle
TOP 1
dafür gäbe , wäre dies der beste Plan. Der Fehler für mich ist, dass die durchschnittliche Selektivität für diese Spalte bei der Schätzung der Zeilen fürTOP 1 WITH TIES
TOP 1
, werden sie für ein Reihenziel verkleinert. SQL Server schätzt, dass dasTOP
Anfordern von Zeilen nach dem Empfang der ersten Zeile beendet wird. Da die ersten 4.424.803 Zeilen des Index-Scans dasselbe Jahr haben, dauert es sogar viel länger.