TL; DR
Da diese Frage immer wieder auftaucht, fasse ich sie hier zusammen, damit Neulinge die Geschichte nicht leiden müssen:
JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2) -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa
Mir ist klar, dass dies möglicherweise nicht jedermanns Problem ist, aber wenn Sie die Empfindlichkeit der ON-Klauseln hervorheben, hilft dies Ihnen möglicherweise, in die richtige Richtung zu schauen. In jedem Fall ist der Originaltext hier für zukünftige Anthropologen:
Original Text
Betrachten Sie die folgende einfache Abfrage (nur 3 betroffene Tabellen)
SELECT
l.sku_id AS ProductId,
l.is_primary AS IsPrimary,
v1.category_name AS Category1,
v2.category_name AS Category2,
v3.category_name AS Category3,
v4.category_name AS Category4,
v5.category_name AS Category5
FROM category c4
JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'
JOIN category c3 ON c3.category_id = c4.parent_category_id
JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'
JOIN category c2 ON c2.category_id = c3.category_id
JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'
JOIN category c1 ON c1.category_id = c2.parent_category_id
JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'
LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang
JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
WHERE c4.[level] = 4 AND c4.version_id = 5
Dies ist eine ziemlich einfache Abfrage. Der einzige verwirrende Teil ist der letzte Kategorie-Join. Dies ist der Fall, da Kategorie-Level 5 möglicherweise vorhanden ist oder nicht. Am Ende der Abfrage suche ich nach Kategoriedaten pro Produkt-ID (SKU-ID) und hier kommt die sehr große Tabelle category_link ins Spiel. Schließlich ist die Tabelle #Ids nur eine temporäre Tabelle mit 10'000 IDs.
Bei der Ausführung erhalte ich den folgenden tatsächlichen Ausführungsplan:
Wie Sie sehen, werden fast 90% der Zeit in den verschachtelten Schleifen (Inner Join) verbracht. Hier sind zusätzliche Informationen zu diesen verschachtelten Schleifen:
Beachten Sie, dass die Tabellennamen nicht genau übereinstimmen, da ich die Abfragetabellennamen aus Gründen der Lesbarkeit bearbeitet habe, die Zuordnung jedoch recht einfach ist (ads_alt_category = category). Gibt es eine Möglichkeit, diese Abfrage zu optimieren? Beachten Sie auch, dass in der Produktion die temporäre Tabelle #Ids nicht existiert, sondern ein Tabellenwertparameter mit denselben 10'000 IDs ist, die an die gespeicherte Prozedur weitergeleitet wurden.
Zusätzliche Information:
- Kategorieindizes für category_id und parent_category_id
- category_voc-Index für category_id, language_code
- category_link-Index für sku_id, category_id
Bearbeiten (gelöst)
Wie aus der akzeptierten Antwort hervorgeht, war das Problem die OR-Klausel im category_link JOIN. Der in der akzeptierten Antwort vorgeschlagene Code ist jedoch sehr langsam und sogar langsamer als der ursprüngliche Code. Eine viel schnellere und auch viel sauberere Lösung besteht einfach darin, die aktuelle JOIN-Bedingung durch Folgendes zu ersetzen:
JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)
Diese minuziöse Optimierung ist die schnellste Lösung, die anhand der Doppelverknüpfung aus der akzeptierten Antwort und anhand der von valverij vorgeschlagenen KREUZANWENDUNG getestet wurde.
quelle
Antworten:
Das Problem scheint in diesem Teil des Codes zu liegen:
or
in Join-Bedingungen ist immer verdächtig. Ein Vorschlag ist, dies in zwei Joins aufzuteilen:Sie müssen dann den Rest der Abfrage ändern, um dies zu handhaben. . .
coalesce(l1.sku_id, l2.sku_id)
zum Beispiel in derselect
Klausel.quelle
JOIN
auf einemCROSS APPLY
mit derIN
Umschaltung auf einEXISTS
in derAPPLY
‚s -WHERE
Klausel.ON l.category_id = ISNULL(c5.category_id, c4.category_id
.coalesce()
der Optimierer in die richtige Richtung bewegt wird.Wie ein anderer Benutzer bereits erwähnt hat, ist dieser Join wahrscheinlich die Ursache:
Sie können diese nicht nur in mehrere Joins aufteilen, sondern auch versuchen, a
CROSS APPLY
Über den obigen MSDN-Link:
Im Grunde
APPLY
ist es wie eine Unterabfrage, bei der Datensätze zuerst rechts herausgefiltert und dann auf den Rest Ihrer Abfrage angewendet werden.In diesem Artikel wird sehr gut erklärt, was es ist und wann es verwendet werden soll: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/
Es ist jedoch wichtig zu beachten, dass die
CROSS APPLY
nicht immer schneller als eineINNER JOIN
. In vielen Situationen wird es wahrscheinlich ungefähr gleich sein. In seltenen Fällen habe ich es jedoch tatsächlich langsamer gesehen (dies hängt wiederum von Ihrer Tabellenstruktur und der Abfrage selbst ab).Als allgemeine Faustregel neige ich dazu, mich zu neigen, wenn ich mit viel zu vielen bedingten Anweisungen an einen Tisch gehe
APPLY
Auch ein lustiger Hinweis:
OUTER APPLY
wird sich wie einLEFT JOIN
Bitte beachten Sie auch meine Wahl zu verwenden,
EXISTS
anstattIN
. Wenn dabeiIN
auf einer Unterabfrage, denken Sie daran , dass es die gesamte Ergebnismenge zurück, selbst nachdem es Ihren Wert gefunden hat. MitEXISTS
wird die Unterabfrage jedoch angehalten, sobald eine Übereinstimmung gefunden wird.quelle
AND x.cat = c4.cat OR x.cat = c5.cat
durchx.cat = ISNULL(c5.cat, c4.cat)
und das Entfernen der IN-Klausel machten dies zur zweitschnellsten Lösung und verdienen eine Aufwertung, da es ziemlich informativ ist.