Aber warum sieht SQL Server nicht beide Abfragen als eine? Immerhin ist ein UND (b ODER c) = (a UND b) ODER (a UND c)?
Logischerweise ist es das gleiche und es werden die gleichen Ergebnisse erzielt.
Annahmen
Ich gehe davon aus, dass der Optimierer für den "schnelleren" Plan einige Filteranweisungen oben nicht als mit einigen Filteranweisungen OR
unten betrachtet. Ich könnte hier völlig außer Kontrolle geraten.
Die Gründe für diese Annahmen basieren auf diesem Filterprädikat:
Dieses Filterprädikat verwendet das Ergebnis der Verknüpfung zwischen Main
Tabelle und manymany
Tabelle.
Beachten Sie, dass EXPR1021 und EXPR1022 in diesem Filter Ausdrücke sind, die vom Skalaroperator in der manymany
Tabelle erstellt wurden.
Dieser Filter besteht aus zwei Teilen, dem ersten mit (.. AND .. OR .. AND ..)
und dem zweiten einfachen AND
Filter
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Wie Sie sehen können, ist der einzige Unterschied über und unter dem OR
im ersten Teil dieses Filters
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Und der zweite Teil muss wahr sein, egal was passiert, da es sich um AND
Prädikate ohne irgendwelche handelt OR
.
Dies führt zu zusätzlichen Berechnungen der gleichen Funktionen, die meiner Meinung nach nicht benötigt werden. Ich vermute auch hier, dass der Grund, warum SQL Server diese Berechnungen durchführt, darin besteht, dass er nicht weiß, dass sie gleich sind.
Für einige andere Teile der where-Klausel ist bekannt, dass diese identisch sind, z. B. wird in der Haupttabelle die statusid = 1 nur einmal ausgewertet:
In der manymany
Tabelle wird dieselbe Aussage zweimal ausgewertet:
Im 'langsamen' Plan werden die Anweisungen nicht zusammen mit OR
Klauseln hinzugefügt. Aus diesem Grund generiert der Optimierer einen anderen Plan, indem Filterprädikate separat auf die Tabellen angewendet werden (und keine doppelten Filter).
Ende der Annahmen
Vergleich der beiden Pläne
Ich denke, dass Sie mit der Leistung des "schnellen" Plans Glück hatten, aber dass der "schnelle" Plan hässlich werden könnte, wenn die übereinstimmenden Daten zunehmen. Dies kann davon abhängen, wo und wann Sie Ihre Filter anwenden (und von anderen Faktoren) .
Die schnelle Planfilterung
Im 'schnellen' Plan: SQL Server wendet einige der Filter nach dem Verknüpfen der main
Tabelle mit der manymany
Tabelle aufgrund unterschiedlicher Kombinationen mit den beiden Blöcken OR
+ ( AND ... AND ... AND...
) an. Die Spalten aus maintable
werden gefiltert, nachdem alle möglichen Kombinationen mit der manymany
Tabelle gefunden wurden.
Infolgedessen wird dasselbe Prädikat zweimal in der manymany
Tabelle ausgeführt:
Für die Prädikate über und unter dem OR
.
Dies ist jedoch bei einigen Suchprädikaten auf dem main
Tisch nicht der Fall
Danach erfolgt die Verknüpfung, und ein noch größeres Filterprädikat für die Ergebnisse der Verknüpfung zwischen main
und manymany
erfolgt erneut für alle möglichen Kombinationen
Beachten Sie, dass EXPR1021 und EXPR1022 in diesem Filter Ausdrücke sind, die vom Skalaroperator in der manymany
Tabelle erstellt wurden.
Dieser Filter besteht aus zwei Teilen, dem ersten mit (.. AND .. OR .. AND ..)
und dem zweiten einfachen AND
Filter
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Wie Sie sehen können, ist der einzige Unterschied über und unter dem OR
im ersten Teil dieses Filters
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Und der zweite Teil muss wahr sein, egal was passiert, da es sich um AND
Prädikate ohne irgendwelche handelt OR
.
Dies führt zu zusätzlichen Berechnungen, die meiner Meinung nach nicht benötigt werden.
Die langsame Planfilterung
Im 'langsamen' Plan: SQL Server wendet den Filter als Ergebnis des AND (TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
Teils) direkt auf die Haupttabelle an und verbindet sich dann mit der manymany
Tabelle, um den Rest herauszufiltern und 15 Zeilen zu erhalten.
Main
Tabellenfilter
manymany
Tabellenfilter
Einige andere, manchmal überlappende Informationen:
Der langsamere Plan
Wenn wir uns den langsameren Plan ansehen, wird der Clustered-Index PK_main in einem Operator für Rechenskalar, Filter und verschachtelte Schleifen verwendet:
Wenn wir dies mit den geschätzten Zeilen vergleichen , die zurückgegeben werden sollen, sehen wir einen Unterschied:
Es werden 93 Zeilen geschätzt, die vom Prädikat beim Scan zurückgegeben werden sollen:
Das ist ungefähr 20x weniger als erwartet, das sind 1947 Zeilen .
Danach der Compute-Skalar oder diese Anweisung:
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
wird auf diesen 1947 Zeilen ausgewertet.
Dann den Filteroperator ( main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL
), um ihn auf 1374 Zeilen zu reduzieren.
Verbinden Sie danach diese 1374 Zeilen mit der dbo.manymany
Tabelle, um 15 Zeilen zurückzugeben.
Der schnellere Plan
Der schnellere Plan verwendet den NC-Index: CVR_main_4
on the dbo.Main
table,
Es wird mit einem Suchprädikat gefiltert, wobei 27 Zeilen an den nested loops
Join-Operator zurückgegeben und erneut mit der dbo.manymany
Tabelle verknüpft werden.
Und die tatsächlich zurückgegebenen Zeilen sind noch niedriger als die geschätzten Zeilen :
27 tatsächliche Zeilen für eine Schätzung von 152 Zeilen
Filtern
Ein großer Unterschied besteht darin, wo die Filterung stattfindet und wo dies beim "langsameren" Plan direkt auf dem dbo.Main
Tisch erfolgt:
Mit dem Prädikat: TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
Und wendet diesen Filter auf 1943 Zeilen an.
Die andere Filterung erfolgt direkt auf dem dbo.manymany
Tisch
(suchen) Prädikate auf dbo.manymany
Während der andere OR
nach dem 'schnelleren' Plan nach dem Join von dbo.Main
bis gefiltert dbo.manymany
wird und in den 27 Zeilen zu einem viel größeren Filter führt.
Viel größerer Filter mit mehreren OR
in 27 Zeilen.
Ein weiterer Unterschied ist der Key Lookup Operator:
Dies erhält 10 zusätzliche Spalten aus dem Clustered-Index, muss dies jedoch nur für 27 Zeilen tun.
Ein weiterer Grund, warum der Optimierer den "langsameren" Plan wählt, könnte sein, dass der Optimierer der Meinung ist, dass es besser wäre, die anderen Spalten nicht nachzuschlagen.
Ist der schnelle Plan noch schneller oder wird er immer "schneller" sein?
Ich denke, wenn die Daten, die durch den Filter fließen, zunehmen, ist der "langsame" Plan besser. Nicht nur aufgrund der Schlüsselsuche, sondern auch aufgrund des größeren Filteroperators weiter unten im Plan.
In diesem Fall neben der Indizierung. Sie können die Filterung verbessern, indem Sie die Abfrage mithilfe einer UNION
Anweisung in mehrere Teile aufteilen.
Wie so:
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol1 IS NOT NULL
UNION
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol2 IS NOT NULL
ORDER BY SortCode;