Wie können Zeilenschätzungen verbessert werden, um die Wahrscheinlichkeit von Verschüttungen auf Tempdb zu verringern?

11

Ich stelle fest, dass die Zeilenschätzungen für einen bestimmten Join häufig weit entfernt sind, wenn Tempdb-Ereignisse verschüttet werden (was zu langsamen Abfragen führt). Ich habe gesehen, dass bei Merge- und Hash-Joins Überlaufereignisse auftreten, die die Laufzeit häufig um das 3-fache bis 10-fache erhöhen. Diese Frage betrifft die Verbesserung der Zeilenschätzungen unter der Annahme, dass dadurch die Wahrscheinlichkeit von Verschüttungsereignissen verringert wird.

Tatsächliche Anzahl der Zeilen 40k.

Für diese Abfrage zeigt der Plan eine schlechte Zeilenschätzung (11,3 Zeilen):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Für diese Abfrage zeigt der Plan eine gute Zeilenschätzung (56.000 Zeilen):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

Können Statistiken oder Hinweise hinzugefügt werden, um die Zeilenschätzungen für den ersten Fall zu verbessern? Ich habe versucht, Statistiken mit bestimmten Filterwerten hinzuzufügen (Eigenschaft = 2840), aber entweder konnte die Kombination nicht korrekt angezeigt werden oder sie wird möglicherweise ignoriert, da die ObjectId zum Zeitpunkt der Kompilierung unbekannt ist und möglicherweise einen Durchschnitt über alle ObjectIds auswählt.

Gibt es einen Modus, in dem die Testabfrage zuerst ausgeführt und dann zur Ermittlung der Zeilenschätzungen verwendet wird, oder muss sie blind fliegen?

Diese bestimmte Eigenschaft hat viele Werte (40 KB) für einige Objekte und Null für die überwiegende Mehrheit. Ich würde mich über einen Hinweis freuen, in dem die maximal erwartete Anzahl von Zeilen für einen bestimmten Join angegeben werden könnte. Dies ist ein allgemeines Problem, da einige Parameter möglicherweise dynamisch als Teil des Joins bestimmt werden oder besser in einer Ansicht platziert werden sollten (keine Unterstützung für Variablen).

Gibt es Parameter, die angepasst werden können, um die Wahrscheinlichkeit von Verschüttungen in Tempdb zu minimieren (z. B. minimaler Speicher pro Abfrage)? Ein robuster Plan hatte keinen Einfluss auf die Schätzung.

Edit 2013.11.06 : Antwort auf Kommentare und zusätzliche Informationen:

Hier sind die Bilder des Abfrageplans. Die Warnungen beziehen sich auf das Kardinalitäts- / Suchprädikat mit convert ():

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Gemäß dem Kommentar von @Aaron Bertrand habe ich versucht, die convert () als Test zu ersetzen:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

Geben Sie hier die Bildbeschreibung ein

Als seltsamer, aber erfolgreicher Punkt von Interesse erlaubte es auch, die Suche kurzzuschließen:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Geben Sie hier die Bildbeschreibung ein

Beide listen eine korrekte Schlüsselsuche auf, aber nur die ersten listen eine "Ausgabe" von ObjectId auf. Ich denke, das deutet darauf hin, dass der zweite tatsächlich kurzgeschlossen ist.

Kann jemand überprüfen, ob jemals einzeilige Sonden durchgeführt werden, um bei Zeilenschätzungen zu helfen? Es scheint falsch, die Optimierung auf nur Histogrammschätzungen zu beschränken, wenn eine einzeilige PK-Suche die Genauigkeit der Suche im Histogramm erheblich verbessern kann (insbesondere wenn Überlaufpotential oder Verlauf vorhanden sind). Wenn eine echte Abfrage 10 dieser Unterverknüpfungen enthält, werden sie im Idealfall parallel ausgeführt.

Eine Randnotiz: Da sql_variant seinen Basistyp (SQL_VARIANT_PROPERTY = BaseType) im Feld selbst speichert, würde ich erwarten, dass eine convert () nahezu kostenlos ist, solange sie "direkt" konvertierbar ist (z. B. keine Zeichenfolge in Dezimalzahl, sondern int in) int oder vielleicht int zu bigint). Da dies zur Kompilierungszeit nicht bekannt ist, aber dem Benutzer möglicherweise bekannt ist, würde eine Funktion "AssumeType (type, ...)" für sql_variants möglicherweise eine transparentere Behandlung ermöglichen.

crokusek
quelle
1
Eine erste Vermutung wäre, dass die Konvertierung in Bigint Ihre Schätzungen abwirft (der Abfrageplan enthält eine Warnung dazu in SQL Server 2012), andererseits könnte Ihre Unterabfrage niemals etwas anderes als 0 oder 1 Zeilen zurückgeben für eine erfolgreiche Abfrage. Es wäre interessant, die Abfragepläne zu sehen, die Sie haben. Vielleicht als Link zur XML-Version.
Mikael Eriksson
2
Was gewinnen Sie, wenn die Unterabfrage inline ist? Ich würde vorschlagen, es separat herauszuziehen, ist insgesamt klarer, und da es zu besseren Schätzungen führt, warum nicht einfach diese Methode verwenden?
Aaron Bertrand
2
Welche Version von SQL Server? Können Sie Tabellen- und Index-DDL- und Statistik-Blobs (ein- und mehrspaltig) für die Tabellen bereitstellen, damit wir die Problemdetails sehen können? Das Aufteilen der Abfrage mit, declare @a bigint = wie Sie es getan haben, scheint mir eine natürliche Lösung zu sein. Warum ist das nicht akzeptabel?
Paul White 9
2
Ich denke, Ihr Design ist ein (sehr vereinfachtes) EAV-Design, das Sie dazu zwingt, es CONVERT()in Spalten zu verwenden und sie dann zu verbinden. Dies ist in den meisten Fällen sicherlich nicht effizient. In diesem Fall muss nur ein Wert konvertiert werden. Dies ist wahrscheinlich kein Problem, aber welche Indizes haben Sie in der Tabelle? EAV-Designs funktionieren normalerweise nur bei ordnungsgemäßer Indizierung gut (was viele Indizes in den normalerweise engen Tabellen bedeutet).
Ypercubeᵀᴹ
@ Paul White, soweit es die Aufteilung betrifft ... das ist eine gute Lösung für diesen Fall. Aber für allgemeinere / komplexere möchte ich Parallelität und Lesbarkeit meistens nicht aufgeben. Angenommen, ich habe 10 davon als Unterabfragen (einige sind komplexer) in einer Abfrage, aber nur 5 müssen "reif" sein, bevor der Rest der Abfrage beginnen kann - ich möchte vermeiden, herausfinden zu müssen, welche 5 dies sind.
Crokusek

Antworten:

7

Ich werde keine Kommentare zu Verschüttungen, Tempdb oder Hinweisen abgeben, da die Abfrage ziemlich einfach zu sein scheint, um so viel Überlegung zu benötigen. Ich denke, der Optimierer von SQL-Server wird seine Arbeit ziemlich gut machen, wenn es Indizes gibt, die für die Abfrage geeignet sind.

Die Aufteilung in zwei Abfragen ist gut, da sie zeigt, welche Indizes nützlich sind. Der erste Teil:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

benötigt einen Index zum (PropertyId, ObjectId, Sequence)Einbeziehen der Value. Ich würde es UNIQUEsicher machen. Die Abfrage würde zur Laufzeit ohnehin einen Fehler auslösen, wenn mehr als eine Zeile zurückgegeben würde. Stellen Sie daher im Voraus sicher, dass dies mit dem eindeutigen Index nicht geschieht:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

Der zweite Teil der Abfrage:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

benötigt einen Index zum (PropertyId, ObjectId)Einbeziehen der Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Wenn die Effizienz nicht verbessert wird oder diese Indizes nicht verwendet wurden oder immer noch Unterschiede bei den Zeilenschätzungen auftreten, muss diese Abfrage genauer untersucht werden.

In diesem Fall sind die Konvertierungen (die für das EAV-Design und das Speichern verschiedener Datentypen in denselben Spalten erforderlich sind) eine wahrscheinliche Ursache, und Ihre Lösung für die Aufteilung (wie @ AAron Bertrand und @ Paul White kommentiert) der Abfrage in zwei Teile erscheint natürlich und der Weg zu gehen. Eine Neugestaltung, um unterschiedliche Datentypen in ihren jeweiligen Spalten zu haben, könnte eine andere sein.

ypercubeᵀᴹ
quelle
Die Tabellen hatten Deckungsindizes - das hätte ich in der Frage angeben sollen. Das Beispiel ist wirklich ein Sub-Join, der eine größere Abfrage füttert, weshalb es so viel Aufhebens um Tempdb-Verschüttungen gibt.
Crokusek
5

Als Teilantwort auf die explizite Frage zur Verbesserung der Statistik ...

Beachten Sie, dass die Zeilenschätzungen selbst für den aufgeschlüsselten Fall immer noch um das 10-fache abweichen (4.000 gegenüber erwarteten 40.000).

Das Statistikhistogramm war für diese Eigenschaft wahrscheinlich zu dünn verteilt, da es sich um eine lange (vertikale) Tabelle mit 3,5 Millionen Zeilen handelt und diese bestimmte Eigenschaft äußerst spärlich ist.

Erstellen Sie eine zusätzliche Statistik (etwas redundant mit der IX-Statistik) für die Eigenschaft sparse:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Die Originale:

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Wenn convert () entfernt wurde (richtig):

Geben Sie hier die Bildbeschreibung ein

Mit entferntem convert () (Kurzschluss):

Geben Sie hier die Bildbeschreibung ein

Immer noch um ~ 2X abweichen, wahrscheinlich, weil> 99,9% der Objekte überhaupt keine Eigenschaft 2840 definiert haben. Tatsächlich existiert die Eigenschaft nur für diesen Testfall nur für 1 von 200.000 verschiedenen Objekten der 3.5M-Zeilentabelle. Es ist erstaunlich, dass es wirklich so nahe gekommen ist. Anpassen des Filters an weniger ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, keine Änderung ... Unterstützt, dass "mit vollem Scan" am Ende der Statistik hinzugefügt wurde (möglicherweise der Grund, warum die beiden vorherigen nicht funktionierten) und ja:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

Geben Sie hier die Bildbeschreibung ein

Yay. In einer stark vertikalen Tabelle mit einem weitgehend abdeckenden IX scheint das Hinzufügen zusätzlicher gefilterter Statistiken eine große Verbesserung zu sein (insbesondere bei spärlichen, aber stark variierenden Tastenkombinationen).

crokusek
quelle
Ein Link zu einigen problematischen Problemen mit mehrspaltigen Statistiken.
Crokusek