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 ():
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);
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);
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.
quelle
declare @a bigint =
wie Sie es getan haben, scheint mir eine natürliche Lösung zu sein. Warum ist das nicht akzeptabel?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).Antworten:
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:
benötigt einen Index zum
(PropertyId, ObjectId, Sequence)
Einbeziehen derValue
. Ich würde esUNIQUE
sicher 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:Der zweite Teil der Abfrage:
benötigt einen Index zum
(PropertyId, ObjectId)
Einbeziehen derValue
: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.
quelle
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:
Die Originale:
Wenn convert () entfernt wurde (richtig):
Mit entferntem convert () (Kurzschluss):
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,
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:
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).
quelle