SQL Server erstellt unterschiedliche Pläne, wenn die ODER-Bedingung neu angeordnet wird

7

Ich habe eine unterdurchschnittliche Abfrage überprüft, die folgendermaßen aussieht:

WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  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 (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode

Ich habe versehentlich den SSMS-Abfrage-Designer für diese Abfrage verwendet und die Abfrage wie folgt neu geschrieben:

WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  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 main.TextCol1 IS NOT NULL

   OR manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P2
  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 main.TextCol2 IS NOT NULL
ORDER BY aux.SortCode

Wenn Sie genau hinsehen werden Sie feststellen , dass es einfach den erweiterten ORZustand durch Wiederholung alle Bedingungen , dh es verändert a AND (b OR c)zu (a AND b) OR (a AND c).

Die resultierende Abfrage war 50% kleiner in Bezug auf die Kosten und 33% kleiner in Bezug auf die Ausführungszeit. Ich verstehe einfach nicht, warum das Neuanordnen der ORBedingung den Plan geändert hat, wenn beide Abfragen identisch sind (?). Ich hätte die ORBedingung selbst erweitern können, indem ich die Bedingungen kopiert habe, aber warum sollte ich das tun?

Fügen Sie den Plan und den Screenshot ein:

Ausführungspläne

Reihenanzahl:

main     2718
manymany 188761
aux      19

Anmerkungen:

  • TextCol1 und TextCol2 sind textDatentypen und können nicht indiziert werden
  • Es gibt Durchschn. 170,20 Datensätze in vielen Tabellen pro Website-ID
Salman A.
quelle
1
Um dies zu verstehen (oder zumindest zu versuchen), müssen Sie die DDL der Tabelle anzeigen. Ein einfaches Beispiel: Wenn zwei separate Indizes vorhanden sind, mit denen die Bedingungen (a UND b) und (a UND c) separat optimiert werden können, können sie in der "erweiterten" Variante verwendet werden, nicht jedoch in der "Basis" -Variante ...
Akina
@Akina Ich habe Indexe für alle where-Klauseln mit Ausnahme der Tabelle, die an IS NOT NULL... diesen Spalten vom textDatentyp sind.
Salman A
Das Hinzufügen der Abfragepläne zu pastetheplan.com wäre hilfreich
Randi Vertongen
Wenn Sie eine ganze Abfrage mit allen Verknüpfungen einfügen, hilft dies jedem, besser zu verstehen. Auch dies hängt von dem Tisch aliased ist main, (wie viele Datensätze jeder Tabelle enthält) in ähnlicher Weise gelten für manymanyund auxAlias - Tabellen und wie Sie sich ihnen anzuschließen.
MarmiK
1
Bitte fügen Sie die CREATE TABLEAnweisungen 9 mit allen Indizes und die vollständige Abfrage hinzu. FROMZumindest die Klausel ist wichtig, um zu verstehen, wie der Plan erstellt werden kann.
Ypercubeᵀᴹ

Antworten:

2

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 ORunten 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 MainTabelle und manymanyTabelle. Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass EXPR1021 und EXPR1022 in diesem Filter Ausdrücke sind, die vom Skalaroperator in der manymanyTabelle erstellt wurden.

Geben Sie hier die Bildbeschreibung ein

Dieser Filter besteht aus zwei Teilen, dem ersten mit (.. AND .. OR .. AND ..) und dem zweiten einfachen ANDFilter

(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 ORim 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 ANDPrä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:

Geben Sie hier die Bildbeschreibung ein

In der manymanyTabelle wird dieselbe Aussage zweimal ausgewertet:

Geben Sie hier die Bildbeschreibung ein

Im 'langsamen' Plan werden die Anweisungen nicht zusammen mit ORKlauseln hinzugefügt. Aus diesem Grund generiert der Optimierer einen anderen Plan, indem Filterprädikate separat auf die Tabellen angewendet werden (und keine doppelten Filter).

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

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 mainTabelle mit der manymanyTabelle aufgrund unterschiedlicher Kombinationen mit den beiden Blöcken OR+ ( AND ... AND ... AND...) an. Die Spalten aus maintablewerden gefiltert, nachdem alle möglichen Kombinationen mit der manymanyTabelle gefunden wurden.

Infolgedessen wird dasselbe Prädikat zweimal in der manymanyTabelle ausgeführt:

Geben Sie hier die Bildbeschreibung ein Für die Prädikate über und unter dem OR.

Dies ist jedoch bei einigen Suchprädikaten auf dem mainTisch nicht der Fall

Geben Sie hier die Bildbeschreibung ein

Danach erfolgt die Verknüpfung, und ein noch größeres Filterprädikat für die Ergebnisse der Verknüpfung zwischen mainund manymanyerfolgt erneut für alle möglichen Kombinationen Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass EXPR1021 und EXPR1022 in diesem Filter Ausdrücke sind, die vom Skalaroperator in der manymanyTabelle erstellt wurden.

Geben Sie hier die Bildbeschreibung ein

Dieser Filter besteht aus zwei Teilen, dem ersten mit (.. AND .. OR .. AND ..) und dem zweiten einfachen ANDFilter

(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 ORim 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 ANDPrä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 NULLTeils) direkt auf die Haupttabelle an und verbindet sich dann mit der manymanyTabelle, um den Rest herauszufiltern und 15 Zeilen zu erhalten.

Main Tabellenfilter

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

manymany Tabellenfilter

Geben Sie hier die Bildbeschreibung ein


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:

Geben Sie hier die Bildbeschreibung ein

Wenn wir dies mit den geschätzten Zeilen vergleichen , die zurückgegeben werden sollen, sehen wir einen Unterschied: Geben Sie hier die Bildbeschreibung ein

Es werden 93 Zeilen geschätzt, die vom Prädikat beim Scan zurückgegeben werden sollen:

Geben Sie hier die Bildbeschreibung ein

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.manymanyTabelle, um 15 Zeilen zurückzugeben.

Der schnellere Plan

Der schnellere Plan verwendet den NC-Index: CVR_main_4on the dbo.Maintable, Geben Sie hier die Bildbeschreibung ein

Es wird mit einem Suchprädikat gefiltert, wobei 27 Zeilen an den nested loopsJoin-Operator zurückgegeben und erneut mit der dbo.manymanyTabelle verknüpft werden.

Und die tatsächlich zurückgegebenen Zeilen sind noch niedriger als die geschätzten Zeilen :

Geben Sie hier die Bildbeschreibung ein

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.MainTisch erfolgt:

Mit dem Prädikat: TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL

Geben Sie hier die Bildbeschreibung ein

Und wendet diesen Filter auf 1943 Zeilen an.

Die andere Filterung erfolgt direkt auf dem dbo.manymanyTisch

Geben Sie hier die Bildbeschreibung ein (suchen) Prädikate auf dbo.manymany

Während der andere ORnach dem 'schnelleren' Plan nach dem Join von dbo.Mainbis gefiltert dbo.manymanywird und in den 27 Zeilen zu einem viel größeren Filter führt.

Geben Sie hier die Bildbeschreibung ein

Viel größerer Filter mit mehreren ORin 27 Zeilen.

Ein weiterer Unterschied ist der Key Lookup Operator:

Geben Sie hier die Bildbeschreibung ein

Dies erhält 10 zusätzliche Spalten aus dem Clustered-Index, muss dies jedoch nur für 27 Zeilen tun.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

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 UNIONAnweisung 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;
Randi Vertongen
quelle
Aber warum sieht SQL Server nicht beide Abfragen als eine? Immerhin a AND (b OR c)= (a AND b) OR (a AND c)?
Salman A
@SalmanA Nehmen wir an, a gehört zur Manymany-Tabelle und B und C gehören zur Haupttabelle. Denn a AND (b OR c)es ähnelt der langsamen Abfrage, bei der boder cdirekt in der Haupttabelle gefiltert wird, um sich dann mit a(ManyMany) zu verbinden, um die zu erhalten verbleibende Ergebnisse. (a and b) or (a and c)filtert nach dem Join der beiden Tabellen, um diese nach Erhalt der Ergebnismenge des Joins herauszufiltern, weil aund ckann nur zusammen mit aund gefiltert werden b.
Randi Vertongen
@SalmanA SQL sieht nicht seine gleich , da 1*1und 1+1ist für SQL nicht gleich. Dies ist am Ende Computer- und Maschinensprache. Wenn es klug genug wird, warum werden Datenbankadministratoren benötigt?!?
MarmiK
2
@RandiVertongen hier erklären Sie, warum die verschiedenen Pläne unterschiedliche Leistung haben und welche besser sein sollten, aber ich die Frage des OP ist, warum das Schreiben von 2 Ausdrücken , die völlig gleichwertig sind, die Pläne des Optimierers beeinflusst. SQL ist (theoretisch) eine deklarative Sprache (nicht zwingend erforderlich), daher teilen wir der Engine mit, was wir wollen und nicht, wie sie funktionieren soll. Für dieses Beispiel scheint es, dass wir das Wie beeinflussen können.
EzLo
@EzLo Sie haben Recht, ich habe meine Annahmen hinzugefügt, aber ich könnte völlig aus der Basis sein.
Randi Vertongen
0

Bei der ersten Abfrage muss mit einem Scan begonnen werden, während bei der zweiten Abfrage der nicht gruppierte Index für eine Suche verwendet werden konnte.

Stellen Sie sich vor, Sie haben 100 Murmeln in einer Tüte und möchten sie prüfen, um nur diejenigen auszuwählen, die blau und weiß oder blau und rot sind.

Die erste Abfrage lautet: Schauen Sie sich die 100 Murmeln an und wählen Sie das gesamte Blau aus. Wenn Sie das getan haben, überprüfen Sie alle und sehen Sie, ob es welche mit Weiß oder Rot gibt.

Die zweite Frage lautet: Geh in die Tasche und nimm nur blaue und weiße oder blaue und rote Murmeln.

Die zweite Abfrage wäre schneller, da Sie nicht zuerst jeden Marmor auf das Blau untersuchen müssten. Sie können diesen Schritt mit dem kombinieren, was Sie wirklich wollten: Blau und Weiß oder Blau und Rot.

So sehe ich es sowieso. Letztendlich erforderte die erste Abfrage einen Tabellenscan und die zweite verwendete die Suche von Anfang an. Es musste noch eine Schlüsselsuche und ein Scan durchgeführt werden, da der nicht gruppierte Index nicht über alle erforderlichen Informationen verfügte. Zu diesem Zeitpunkt musste jedoch ein viel kleinerer Datensatz durchsucht werden, sodass er schneller war.

Muab Nhoj
quelle
0

Plan Link funktioniert nicht.

Sie haben unvollständige Informationen angegeben. Es ist wichtig, die Tabellenverknüpfungsspalte und ihren Datentyp zu kennen.

WAS ist aux.SortCode? WAS ist auxAlias? Wo ist es in Query?

Es gibt weitere Möglichkeiten, die Abfrage neu anzuordnen.

In diesem Fall,

AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)

SQL Optimizer Cardianility Estimateist sehr schlecht.

Dieselbe Bedingung mit zu wiederholen ORist eine schlechte Idee.

Sie können manyTabellendatensatz in #TempTabelle oder setzenCTE

Dann endlich manymanymit #TempTisch verbinden.

DECLARE @Dt Datetime=CURRENT_TIMESTAMP

select many.Col,many.OnlyRequiredColumn
from many into #Temp
WHERE main.Active = -1
  AND main.StatusID = 1
  AND (main.FromDate>=@Dt AND main.UptoDate<=@Dt)
  AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode

Wenn Sie #Tempmehr als 100/200 indexDatensätze haben, können Sie in der Spalte erstellen, die sich mit vielen verbinden.

select *
from manymany
join #Temp on manymany.somecolumn=#temp.somecolumn
WHERE manymany.Active = -1
  AND manymany.Check1 = -1
  AND manymany.WebsiteID = @P1
  AND (manymany.FromDate>=@Dt AND manymany.UptoDate<=@Dt)
--ORDER BY aux.SortCode
KumarHarsh
quelle