T-SQL-Abfrage mit einem völlig anderen Plan, abhängig von der Anzahl der Zeilen, die ich aktualisiere

20

Ich habe eine SQL UPDATE-Anweisung mit einer "TOP (X)" - Klausel, und die Zeile, in der ich Werte aktualisiere, enthält ungefähr 4 Milliarden Zeilen. Wenn ich "TOP (10)" verwende, erhalte ich einen Ausführungsplan, der fast sofort ausgeführt wird. Wenn ich jedoch "TOP (50)" oder höher verwende, wird die Abfrage nie (zumindest nicht, solange ich warte) beendet Es wird ein völlig anderer Ausführungsplan verwendet. Die kleinere Abfrage verwendet einen sehr einfachen Plan mit einem Paar von Indexsuchen und einem Join mit verschachtelten Schleifen, wobei genau dieselbe Abfrage (mit einer anderen Anzahl von Zeilen in der TOP-Klausel der UPDATE-Anweisung) einen Plan verwendet, der zwei verschiedene Indexsuchen umfasst , eine Tabellenspule, Parallelität und eine Menge anderer Komplexität.

Ich habe "OPTION (USE PLAN ...)" verwendet, um zu erzwingen, dass der von der kleineren Abfrage generierte Ausführungsplan verwendet wird. Wenn ich dies tue, kann ich in wenigen Sekunden bis zu 100.000 Zeilen aktualisieren. Ich weiß, dass der Abfrageplan gut ist, aber SQL Server wählt diesen Plan nur dann alleine aus, wenn nur eine geringe Anzahl von Zeilen betroffen ist. Jede anständig große Zeilenanzahl in meinem Update führt zu einem suboptimalen Plan.

Ich dachte, die Parallelität könnte schuld sein, also stellte ich MAXDOP 1die Abfrage ein, aber ohne Wirkung - dieser Schritt ist weg, aber die schlechte Wahl / Leistung ist es nicht. Ich bin auch sp_updatestatserst heute Morgen gelaufen, um sicherzugehen, dass das nicht die Ursache ist.

Ich habe die beiden Ausführungspläne angehängt - der kürzere ist auch der schnellere. Außerdem ist hier die betreffende Abfrage (es ist erwähnenswert, dass das von mir eingeschlossene SELECT sowohl bei kleinen als auch bei großen Zeilenzahlen schnell zu sein scheint):

    update top (10000) FactSubscriberUsage3
               set AccountID = sma.CustomerID
    --select top 50 f.AccountID, sma.CustomerID
      from FactSubscriberUsage3 f
      join dimTime t
        on f.TimeID = t.TimeID
      join #mac sma
        on f.macid = sma.macid
       and t.TimeValue between sma.StartDate and sma.enddate 
     where f.AccountID = 0 --There's a filtered index on the table for this

Hier ist der schnelle Plan : Schnellausführungsplan

Und hier ist die langsamere : Slow Execution Plan

Gibt es irgendetwas Offensichtliches in der Art und Weise, wie ich meine Abfrage einrichte, oder im Ausführungsplan, sofern es sich für die schlechte Wahl eignet, die die Abfrage-Engine trifft? Bei Bedarf kann ich auch die beteiligten Tabellendefinitionen und die darauf definierten Indizes einfügen.

An diejenigen, die nach einer Nur-Statistiken-Version der Datenbankobjekte fragten: Ich wusste nicht einmal, dass Sie das tun können, aber es ist absolut sinnvoll! Ich habe versucht, die Skripte für eine reine Statistikdatenbank zu generieren, damit andere die Ausführungspläne selbst testen können, aber ich kann auf meinem gefilterten Index Statistiken / Histogramme generieren (Syntaxfehler im Skript, so scheint es) aus Glück dort. Ich habe versucht, den Filter zu entfernen, und die Abfragepläne waren eng, aber nicht genau gleich, und ich möchte niemanden auf eine Gänsehaut schicken.

Update und einige vollständigere Ausführungspläne: Zunächst einmal ist der Plan Explorer von SQL Sentry ein unglaubliches Tool. Ich wusste nicht einmal, dass es sie gibt, bis ich die anderen Fragen zum Abfrageplan auf dieser Site gelesen habe, und es gab einiges darüber zu sagen, wie meine Abfragen ausgeführt wurden. Obwohl ich nicht sicher bin, wie ich das Problem angehen soll, haben sie deutlich gemacht, woran es liegt.

Hier ist die Zusammenfassung für 10, 100 und 1000 Zeilen - Sie können sehen, dass die 1000-Zeilen-Abfrage weit davon entfernt ist, mit den anderen übereinzustimmen: Anweisungszusammenfassung

Sie können sehen, dass die dritte Abfrage eine lächerliche Anzahl von Lesevorgängen aufweist, sodass sie offensichtlich etwas völlig anderes ausführt. Hier ist der geschätzte Ausführungsplan mit Zeilenzahlen. Geschätzter Ausführungsplan für 1000 Zeilen: Geschätzter Ausführungsplan für 1000 Zeilen

Und hier sind die tatsächlichen Ergebnisse des Ausführungsplans (übrigens mit "nie beendet", es stellte sich heraus, dass ich "in einer Stunde beendet" meinte). Tatsächlicher Ausführungsplan mit 1000 Zeilen Tatsächlicher Ausführungsplan mit 1000 Zeilen

Das erste, was mir aufgefallen ist, ist, dass, anstatt wie erwartet 60.000 Zeilen aus der dimTime-Tabelle zu ziehen, tatsächlich 1,6 Milliarden mit einem B gezogen werden . Wenn ich meine Abfrage betrachte, bin ich mir nicht sicher, wie viele Zeilen aus der dimTime-Tabelle zurückgezogen werden. Der BETWEEN-Operator, den ich verwende, stellt lediglich sicher, dass ich den richtigen Datensatz aus #mac basierend auf dem Zeitdatensatz in der Faktentabelle ziehe. Wenn ich jedoch der WHERE-Klausel eine Zeile hinzufüge, in der ich t.TimeValue (oder t.TimeID) nach einem einzelnen Wert filtere, kann ich innerhalb von Sekunden 100.000 Zeilen erfolgreich aktualisieren. Infolgedessen und wie in den Ausführungsplänen, die ich beigefügt habe, verdeutlicht, ist es offensichtlich, dass mein Zeitplan das Problem ist, aber ich bin nicht sicher, wie ich die Join-Kriterien ändern würde, um dieses Problem zu umgehen und die Genauigkeit zu gewährleisten . Irgendwelche Gedanken?

Als Referenz hier der Plan (mit Zeilenzahlen) für die Aktualisierung von 100 Zeilen. Sie können sehen, dass es auf den gleichen Index trifft und immer noch eine Tonne Zeilen enthält, aber bei weitem nicht das gleiche Ausmaß eines Problems aufweist. 100 Zeilen Ausführung mit Zeilenanzahl : Bildbeschreibung hier eingeben

SqlRyan
quelle
Es ist GOTTA Be Statistik. Hast du einen sp_updatestatisticsauf den Tisch laufen lassen ?
JNK,
@JNK: Das habe ich mir ursprünglich gedacht, aber sp_updatestats wurde bereits ohne Änderung ausgeführt. Ich habe es gerade noch einmal ausgeführt und es war mir egal, ob die Statistiken für einen der an der Abfrage beteiligten Indizes aktualisiert wurden. Trotzdem danke!
SqlRyan
Der zweite ist ein umfassender (pro Index) Aktualisierungsplan und kein enger (pro Zeile) Aktualisierungsplan, der einige der zusätzlich sichtbaren Komplexitäten erklärt. Aber wirklich der einzige Unterschied ist die Reihenfolge der Beitritte from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddategegenfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
Martin Smith
Hier stimmt etwas nicht. Auch der teure Abfrageplan sollte Zeilen inkrementell generieren. A TOP 50sollte immer noch schnell ausgeführt werden. Können Sie die XML-Pläne hochladen? Ich muss mir die Zeilenzahlen ansehen. Kannst du das TOP 50mit maxdop 1 und als select, nicht als update ausführen und den plan posten? (Versuch, den Suchraum zu vereinfachen / zu halbieren).
usr
Beim Beitritt von @usr zu werden, kann t.TimeValue between sma.StartDate and sma.enddatedazu führen, dass viel mehr nutzlose Zeilen generiert werden, die später im Join gegen FactSubscriber herausgefiltert werden und daher nicht im Endergebnis enden.
Martin Smith

Antworten:

3

Der Index für dimTime ändert sich. Der schnellere Plan verwendet einen _dta-Index. Stellen Sie zunächst sicher, dass in sys.indexes kein hypothetischer Index angegeben ist.

Denken Sie, Sie könnten einige Parametrisierungen umgehen, indem Sie die #mac-Tabelle zum Filtern verwenden, anstatt nur das Start- / Enddatum wie dieses anzugeben. WHERE t.TimeValue zwischen @StartDate und @enddate. Werde die temporäre Tabelle los.

william_a_dba
quelle
Der Index mit dem Präfix "dta" sieht aus, als ob er anhand einer DTA-Empfehlung erstellt wurde, ohne den Namen anzupassen. Hypothetische Indizes können in tatsächlichen Ausführungsplänen nicht angezeigt werden (und werden auch ohne einige undokumentierte Befehle nicht geschätzt). Ich bin mir nicht sicher, wie Ihr zweiter Vorschlag funktionieren würde. t.TimeValue between sma.StartDate and sma.enddateist korreliert, kann sich also für jede Zeile in der #tempTabelle ändern . Womit würde das OP es ersetzen?
Martin Smith
Fair genug, ich habe nicht genug auf die temporäre Tabelle geachtet.
William_a_dba
1
Hypothetische Indizes können jedoch tatsächlich einen Ausführungsplan vermasseln. Wenn es hypothetisch ist, sollte es gelöscht und neu erstellt werden. blogs.technet.com/b/anurag_sharma/archive/2008/04/15/…
william_a_dba
Hypothetische Indizes bleiben übrig, wenn der DTA vor Abschluss nicht beendet / eingefroren wird. Sie müssen manuell bereinigt werden, wenn ein Problem mit dem DTA vorliegt.
William_a_dba
1
@william_a_dba - Ah, ich verstehe, was du jetzt meinst (nachdem du deinen Link gelesen hast). Die Abfrage wurde nie fertiggestellt, es könnte sein, dass sie ständig neu kompiliert wird. Interessant!
Martin Smith
1

Ohne weitere Informationen zu den Zeilenzahlen im Plan ist es meine vorläufige Empfehlung, die richtige Verknüpfungsreihenfolge in der Abfrage zu arrangieren und sie mit zu erzwingen OPTION (FORCE ORDER). Erzwingen Sie die Verknüpfungsreihenfolge des ersten Plans.

usr
quelle