Warum führt das Ändern der deklarierten Join-Spaltenreihenfolge eine Sortierung ein?

40

Ich habe zwei Tabellen mit identisch benannten, typisierten und indizierten Schlüsselspalten. Einer von ihnen hat einen eindeutigen Clustered-Index, der andere einen nicht eindeutigen .

Der Testaufbau

Setup-Skript, einschließlich einiger realistischer Statistiken:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

Der Repro

Wenn ich diese beiden Tabellen mit ihren Clustering-Schlüsseln verbinde, erwarte ich eine Eins-zu-viele-MERGE-Verknüpfung wie folgt:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

Dies ist der Abfrageplan, den ich möchte:

Das ist was ich will.

(Egal welche Warnungen, sie haben mit den gefälschten Statistiken zu tun.)

Wenn ich jedoch die Reihenfolge der Spalten in der Verknüpfung ändere, geschieht Folgendes:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... das passiert:

Der Abfrageplan nach dem Ändern der deklarierten Spaltenreihenfolge im Join.

Der Sortieroperator scheint die Streams in der angegebenen Reihenfolge des Joins zu ordnen, dh er c, a, b, d, e, f, g, hfügt meinem Abfrageplan eine Blockierungsoperation hinzu.

Dinge, die ich angeschaut habe

  • Ich habe versucht, die Spalten auf NOT NULLdieselben Ergebnisse zu ändern .
  • Die ursprüngliche Tabelle wurde mit erstellt ANSI_PADDING OFF, aber das Erstellen mit ANSI_PADDING ONhat keine Auswirkungen auf diesen Plan.
  • Ich habe INNER JOINstattdessen versucht LEFT JOIN, keine Veränderung.
  • Ich entdeckte es auf einem 2014 SP2 Enterprise, erstellte einen Repro auf einem 2017 Developer (aktuelle CU).
  • Das Entfernen der WHERE-Klausel in der führenden Indexspalte führt zwar zu einem guten Plan, wirkt sich jedoch auf die Ergebnisse aus. :)

Schließlich kommen wir zu der Frage

  • Ist das beabsichtigt?
  • Kann ich die Sortierung entfernen, ohne die Abfrage zu ändern (das ist der Herstellercode, also würde ich es lieber nicht tun ...)? Ich kann die Tabelle und die Indizes ändern.
Daniel Hutmacher
quelle

Antworten:

28

Ist das beabsichtigt?

Es ist beabsichtigt, ja. Die beste öffentliche Quelle für diese Behauptung ging leider verloren, als Microsoft die Connect-Feedback-Website zurückzog und viele nützliche Kommentare von Entwicklern im SQL Server-Team verwischte.

Auf jeden Fall versucht das aktuelle Optimierungsdesign nicht , unnötige Sortierungen per se aktiv zu vermeiden . Dies tritt am häufigsten bei Fensterfunktionen und dergleichen auf, kann jedoch auch bei anderen Operatoren beobachtet werden, die für die Reihenfolge und insbesondere für die Beibehaltung der Reihenfolge zwischen Operatoren empfindlich sind.

Trotzdem kann der Optimierer (in vielen Fällen) eine unnötige Sortierung ganz gut vermeiden, aber dieses Ergebnis tritt normalerweise aus anderen Gründen auf als aus aggressiven Gründen, die unterschiedliche Sortierkombinationen versuchen. In diesem Sinne ist es weniger eine Frage des "Suchraums" als vielmehr der komplexen Wechselwirkungen zwischen orthogonalen Optimierungsmerkmalen, von denen gezeigt wurde, dass sie die allgemeine Planqualität zu akzeptablen Kosten steigern.

Beispielsweise kann das Sortieren häufig einfach dadurch vermieden werden, dass eine Sortieranforderung (z. B. oberste Ebene ORDER BY) mit einem vorhandenen Index abgeglichen wird. In Ihrem Fall könnte dies zwar das Hinzufügen bedeuten ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;, dies ist jedoch eine übermäßige Vereinfachung (und inakzeptabel, da Sie die Abfrage nicht ändern möchten).

Allgemeiner kann jede Memogruppe erforderlichen oder gewünschten Eigenschaften zugeordnet sein, die eine Eingabereihenfolge umfassen können. Wenn es keinen offensichtlichen Grund gibt , einen bestimmten Befehl durchzusetzen (z. B. um einen Auftrag zu erfüllen ORDER BYoder um korrekte Ergebnisse von einem auftragssensitiven physischen Bediener sicherzustellen), liegt ein Element des Glücks vor. Ich habe in Vermeiden von Sortierungen mit Zusammenführungsverknüpfungen mehr über die Besonderheiten des Zusammenführens von Verknüpfungen (im Vereinigungs- oder Verknüpfungsmodus) geschrieben . Vieles davon geht über die unterstützte Oberfläche des Produkts hinaus. Behandeln Sie es daher als informativ und unterliegen Sie Änderungen.

Ja, in Ihrem speziellen Fall können Sie die Indizierung anpassen, wie es jadarnel27 vorschlägt , um die Sortierungen zu vermeiden. Es gibt jedoch wenig Grund, hier einen Merge-Join zu bevorzugen. Sie können auch eine Wahl zwischen einer physischen Hash- oder Loop-Verknüpfung unter OPTION(HASH JOIN, LOOP JOIN)Verwendung eines Plan-Leitfadens andeuten, ohne die Abfrage zu ändern, je nach Kenntnis der Daten und dem Kompromiss zwischen bester, schlechtester und durchschnittlicher Leistung.

Beachten Sie zum Schluss, dass die Sortierung mit einem einfachen ORDER BY l.b, auf Kosten eines möglicherweise weniger effizienten Zusammenschlusses von vielen zu vielen, der ballein mit einem komplexen Residuum verbunden ist, vermieden werden kann . Ich erwähne dies hauptsächlich, um die Interaktion zwischen den Optimierungsfunktionen, die ich zuvor erwähnt habe, und der Art und Weise zu veranschaulichen, wie sich Anforderungen auf höchster Ebene verbreiten können.

Paul White
quelle
19

Kann ich die Sortierung entfernen, ohne die Abfrage zu ändern (das ist der Herstellercode, also würde ich es lieber nicht tun ...)? Ich kann die Tabelle und die Indizes ändern.

Wenn Sie die Indizes ändern können #right, wird die Sortierung (für mich) entfernt, wenn Sie die Reihenfolge des Indexes so ändern , dass sie mit der Reihenfolge der Filter im Join übereinstimmt:

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

Überraschenderweise (zumindest für mich) führt dies dazu, dass keine Abfrage mit einer Sortierung endet.

Ist das beabsichtigt?

Betrachtet man die Ausgabe einiger seltsamer Ablaufverfolgungsflags , so ergibt sich ein interessanter Unterschied in der endgültigen Memo-Struktur:

Screenshot der endgültigen Memostruktur für jede Abfrage

Wie Sie oben in der "Stammgruppe" sehen können, haben beide Abfragen die Option, einen Zusammenführungs-Join als die physische Hauptoperation zum Ausführen dieser Abfrage zu verwenden.

Gute Abfrage

Der Join ohne Sortierung wird von Option 1 der Gruppe 29 und Option 1 der Gruppe 31 gesteuert (wobei es sich jeweils um Bereichsüberprüfungen der beteiligten Indizes handelt). Es wird nach Gruppe 27 (nicht gezeigt) gefiltert, bei der es sich um die Reihe logischer Vergleichsoperationen handelt, die den Join filtern.

Falsche Abfrage

Das mit der Sortierung wird durch die (neuen) Optionen 3 gesteuert, die jede dieser beiden Gruppen (29 und 31) hat. Option 3 führt eine physische Sortierung der Ergebnisse der zuvor genannten Bereichsscans durch (Option 1 jeder dieser Gruppen).

Warum?

Aus irgendeinem Grund steht die Option, 29.1 und 31.1 direkt als Quellen für den Zusammenführungs-Join zu verwenden, dem Optimierer in der zweiten Abfrage nicht einmal zur Verfügung. Andernfalls würde es meiner Meinung nach unter den anderen Optionen unter der Stammgruppe aufgeführt. Wenn es überhaupt verfügbar wäre, würde es definitiv die über die massiv teureren Sortiervorgänge auswählen.

Daraus kann ich nur schließen:

  • Dies ist ein Fehler (oder eher eine Einschränkung) im Suchalgorithmus des Optimierers
    • Durch Ändern der Indizes und Joins auf nur 5 Schlüssel wird die Sortierung für die zweite Abfrage entfernt (6, 7 und 8 Schlüssel haben alle die Sortierung).
    • Dies impliziert, dass der Suchraum mit 8 Schlüsseln so groß ist, dass das Optimierungsprogramm keine Zeit hat, die nicht sortierte Lösung als praktikable Option zu identifizieren, bevor sie mit dem Grund "Gut genug Plan gefunden" vorzeitig beendet wird
    • Es scheint mir ein bisschen verworren zu sein, dass die Reihenfolge der Verknüpfungsbedingungen den Suchprozess des Optimierers so stark beeinflusst, aber das ist wirklich ein bisschen zu viel für mich
  • Die Sortierung ist erforderlich, um die Richtigkeit der Ergebnisse zu gewährleisten
    • dieser scheint unwahrscheinlich, da die Abfrage kann , ohne die Art ausgeführt werden, wenn es weniger Tasten oder die Tasten werden in einer anderen Reihenfolge sind

Hoffentlich kann jemand mitkommen und erklären, warum die Sortierung erforderlich ist, aber ich fand den Unterschied im Memo-Gebäude interessant genug, um als Antwort zu posten.

Josh Darnell
quelle
1
Ich glaube, Ihr Kommentar zum Suchraum ist hier tatsächlich der Fall. Um nur die Indizes zu verwenden, muss das Optimierungsprogramm überprüfen, ob sie für die Bedingungen ausreichend sind. Nach 5 Schlüsseln gibt es zu viele Möglichkeiten, um zu überprüfen, bevor es zurückgreifen muss. Ich wäre neugierig, wenn alle Auftragskombinationen der Abfrage aufgezählt würden, wie viele der Optimierer erfolgreich sein würden,
anstatt
Und ja, die Inkonsistenz scheint ein wenig fehlerhaft zu sein, hängt aber wahrscheinlich vollständig von dem Algorithmus ab, mit dem überprüft wird, ob die Indizes ausreichen. Wenn alle Kombinationen getestet würden, könnten Sie wahrscheinlich das Muster in den Ergebnissen sehen und bestimmen, welcher Algorithmus verwendet wird. Ich wette, es wurde geschrieben, um für die typischen Anwendungsfälle eine optimale Leistung zu erzielen. Möglicherweise gibt es eine Alternative, mit der die 8-Schlüssel-Lösung innerhalb des Zeitlimits zuverlässig gefunden werden kann. Sie ist jedoch langsamer als die aktuelle Lösung, wenn weniger als 3-4 Schlüssel vorhanden sind.
Mr.Mindor