Ich habe eine ziemlich einfache Frage
SELECT TOP 1 dc.DOCUMENT_ID,
dc.COPIES,
dc.REQUESTOR,
dc.D_ID,
cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Das ist eine schreckliche Leistung für mich (als hätte ich nie darauf gewartet, dass es zu Ende geht). Der Abfrageplan sieht folgendermaßen aus:
Wenn ich jedoch das entferne, TOP 1
erhalte ich einen Plan, der so aussieht und in 1-2 Sekunden ausgeführt wird:
Korrigieren Sie PK & Indizierung unten.
Die Tatsache, dass TOP 1
sich der Abfrageplan geändert hat, überrascht mich nicht. Ich bin nur ein bisschen überrascht, dass es so viel schlimmer ist.
Hinweis: Ich habe die Ergebnisse dieses Beitrags gelesen und das Konzept eines Row Goal
usw. verstanden. Ich bin gespannt, wie ich die Abfrage so ändern kann, dass sie den besseren Plan verwendet. Momentan speichere ich die Daten in einer temporären Tabelle und ziehe dann die erste Zeile heraus. Ich frage mich, ob es eine bessere Methode gibt.
Bearbeiten Für Leute, die dies nachträglich lesen, gibt es hier ein paar zusätzliche Informationen.
- Document_Queue - PK / CI ist D_ID und hat ~ 5k Zeilen.
- Correspondence_Journal - PK / CI ist FILE_NUMBER, CORRESPONDENCE_ID und hat ~ 1,4 Millionen Zeilen.
Als ich anfing, gab es keine anderen Indizes. Ich landete mit einem auf Correspondence_Journal (Document_Id, File_Number)
quelle
DOCUMENT_ID
Beziehung zwischen den beiden Tabellen erzwingt (oder hat jeder Datensatz inCORRESPONDENCE_JOURNAL
einen übereinstimmenden Datensatz inDOCUMENT_QUEUE
)?Antworten:
Versuchen Sie, einen Hash- Join zu erzwingen *
Der Optimierer dachte wahrscheinlich, dass eine Schleife mit Top 1 besser werden würde und das macht Sinn, aber in Wirklichkeit hat es hier nicht funktioniert. Nur eine Vermutung hier, aber vielleicht waren die geschätzten Kosten für diesen Spool weg - er verwendet TEMPDB - Sie haben möglicherweise eine TEMPDB mit schlechter Leistung.
* Seien Sie vorsichtig mit Verknüpfungshinweisen , da diese die Zugriffsreihenfolge für Plantabellen erzwingen, damit sie mit der schriftlichen Reihenfolge der Tabellen in der Abfrage übereinstimmen (so als ob
OPTION (FORCE ORDER)
sie angegeben worden wären ). Über den Dokumentationslink:Dies kann im Beispiel keine unerwünschten Wirkungen hervorrufen, kann aber im Allgemeinen sehr gut sein.
FORCE ORDER
(implizit oder explizit) ist ein sehr mächtiger Hinweis, der über die Durchsetzung der Ordnung hinausgeht. Dadurch wird verhindert, dass eine breite Palette von Optimierungsverfahren angewendet wird, einschließlich Teilaggregationen und Neuanordnungen.Eine
OPTION (HASH JOIN)
Abfrage Hinweis darauf sein kann , weniger aufdringlich in geeigneten Fällen, da dies bedeutet nichtFORCE ORDER
. Dies gilt jedoch für alle Joins in der Abfrage. Andere Lösungen sind verfügbar.quelle
Da Sie den richtigen Plan mit dem bekommen
ORDER BY
, könnten Sie vielleicht einfach Ihren eigenenTOP
Operator rollen ?Meiner Meinung nach sollte der Abfrageplan für das
ROW_NUMBER()
oben Genannte derselbe sein, als ob Sie einen hattenORDER BY
. Der Abfrageplan sollte jetzt ein Segment, ein Sequenzprojekt und schließlich einen Filteroperator enthalten. Der Rest sollte genauso aussehen wie Ihr guter Plan.quelle
Bearbeiten: +1 funktioniert in dieser Situation, da sich herausstellt, dass
FILE_NUMBER
es sich um eine mit Nullen aufgefüllte Zeichenfolgenversion einer Ganzzahl handelt. Eine bessere Lösung für Zeichenfolgen ist das Anhängen''
(der leere String), da das Anhängen eines Werts die Reihenfolge beeinflussen kann, oder das Hinzufügen einer Konstante, die jedoch eine nicht deterministische Funktion enthält, wie zsign(rand()+1)
. Die Idee, die Sorte zu durchbrechen, ist hier immer noch gültig, nur dass meine Methode nicht ideal war.+1
Nein, ich meine nicht, dass ich mit irgendetwas einverstanden bin, ich meine das als Lösung. Wenn Sie Ihre Abfrage ändern , um
ORDER BY cj.FILE_NUMBER + 1
dann dasTOP 1
wird sich anders verhalten.Wie Sie sehen, versucht das System mit dem kleinen Zeilenziel für eine geordnete Abfrage, die Daten der Reihe nach zu verarbeiten, um einen Sortieroperator zu vermeiden. Es wird auch vermieden, eine Hash-Tabelle zu erstellen, da es wahrscheinlich nicht zu viel Arbeit erfordert, um diese erste Zeile zu finden. In Ihrem Fall ist dies falsch - von der Dicke dieser Pfeile aus sieht es so aus, als müsste eine Menge Daten verbraucht werden, um eine einzelne Übereinstimmung zu finden.
Die Dicke dieser Pfeile deutet darauf hin, dass Ihre
DOCUMENT_QUEUE
(DQ) -Tabelle viel kleiner als IhreCORRESPONDENCE_JOURNAL
(CJ) -Tabelle ist. Und dass der beste Plan tatsächlich darin besteht, die DQ-Zeilen zu durchsuchen, bis eine CJ-Zeile gefunden wird. Genau das würde der Query Optimizer (QO) tun, wenn er nicht so nervig wäre. DiesORDER BY
wird durch einen CJ-Covering-Index unterstützt.Wenn Sie die Datei also
ORDER BY
vollständig löschen, erhalten Sie wahrscheinlich einen Plan, der eine verschachtelte Schleife umfasst, die die Zeilen in DQ durchläuft und nach CJ sucht, um sicherzustellen, dass die Zeile vorhanden ist. Und mitTOP 1
würde dies aufhören, nachdem eine einzelne Reihe gezogen wurde.Wenn Sie jedoch tatsächlich die erste Zeile in der richtigen
FILE_NUMBER
Reihenfolge benötigen , können Sie das System dazu verleiten, den Index zu ignorieren, der (fälschlicherweise) so hilfreich zu sein scheint.ORDER BY CJ.FILE_NUMBER+1
Wir wissen, dass er die gleiche Reihenfolge wie zuvor, aber vor allem die QO beibehält nicht. Die Qualitätssicherung wird sich darauf konzentrieren, das Ganze darzulegen, damit ein Top-N-Sortieroperator zufrieden sein kann. Diese Methode sollte einen Plan erstellen, der einen Compute Scalar-Operator zum Ermitteln des Bestellwerts und einen Top N Sort-Operator zum Abrufen der ersten Zeile enthält. Aber auf der rechten Seite sollten Sie eine schöne verschachtelte Schleife sehen, die viele Suchvorgänge auf CJ ausführt. Und eine bessere Leistung als das Durchsuchen einer großen Zeilentabelle, die mit nichts in DQ übereinstimmt.Das Hash-Match ist nicht unbedingt furchtbar, aber wenn die Menge der Zeilen, die Sie von DQ zurückgeben, viel kleiner als CJ ist (wie ich es erwarten würde), wird das Hash-Match viel mehr CJ scannen als es braucht.
Hinweis: Ich habe +1 anstelle von +0 verwendet, da das Abfrageoptimierungsprogramm wahrscheinlich erkennt, dass +0 nichts ändert. Natürlich könnte das Gleiche für die +1 gelten, wenn nicht jetzt, dann irgendwann in der Zukunft.
quelle
Durch
OPTION (QUERYTRACEON 4138)
das Hinzufügen wird die Wirkung von Zeilenzielen nur für diese Abfrage deaktiviert, ohne dass der endgültige Plan zu genau festgelegt wird. Dies ist wahrscheinlich der einfachste / direkteste Weg.Wenn Sie durch Hinzufügen dieses Hinweises einen Berechtigungsfehler erhalten (erforderlich für
DBCC TRACEON
), können Sie ihn mithilfe eines Planungshandbuchs anwenden:Verwendung
QUERYTRACEON
in Planführern von Spaghettidba... oder verwenden Sie einfach eine gespeicherte Prozedur:
Welche Berechtigungen sind erforderlich
QUERYTRACEON
? von Kendra Littlequelle
Neuere Versionen von SQL Server bieten verschiedene (und möglicherweise bessere) Optionen für die Verarbeitung von Abfragen, die eine suboptimale Leistung erzielen, wenn das Optimierungsprogramm Zeilenzieloptimierungen anwenden kann. In SQL Server 2016 SP1 wurde das eingeführt,
DISABLE_OPTIMIZER_ROWGOAL USE HINT
das den gleichen Effekt wie das Ablaufverfolgungsflag 4138 hat. Wenn Sie nicht in dieser Version sind, können Sie auch denOPTIMIZE FOR
Abfragehinweis verwenden, um einen Abfrageplan zu erhalten, der alle Zeilen anstelle von nur 1 zurückgibt gibt die gleichen Ergebnisse wie das in der Frage angegebene zurück, wird jedoch nicht mit dem Ziel erstellt, nur eine Zeile zu erhalten.quelle
Da du a machst
TOP(1)
, empfehle ich, dasORDER BY
Deterministische für den Anfang zu machen. Zumindest wird so sichergestellt, dass die Ergebnisse funktionell vorhersehbar sind (immer nützlich für Regressionstests). Es sieht so aus, als müssten Sie hinzufügenDC.D_ID
undCJ.CORRESPONDENCE_ID
dafür.Bei der Betrachtung von Abfrageplänen finde ich es manchmal aufschlussreich, die Abfrage zu vereinfachen: Wählen Sie möglicherweise alle relevanten DC-Zeilen im Voraus in einer temporären Tabelle aus, um Probleme mit der Kardinalitätsschätzung für
QUEUE_DATE
und zu beseitigenPRINT_LOCATION
. Dies sollte angesichts der geringen Zeilenanzahl schnell gehen. Sie können dann bei Bedarf Indizes zu dieser temporären Tabelle hinzufügen, ohne die permanente Tabelle zu ändern.quelle