Warum wird die Kardinalität von LEN () in SQL Server 2014 stark unterschätzt?

26

Ich habe eine Tabelle mit einer Zeichenfolgenspalte und einem Prädikat, das nach Zeilen mit einer bestimmten Länge sucht. In SQL Server 2014 wird unabhängig von der Länge, auf die ich prüfe, eine Schätzung von 1 Zeile angezeigt. Dies führt zu sehr schlechten Plänen, da tatsächlich Tausende oder sogar Millionen von Zeilen vorhanden sind und SQL Server diese Tabelle auf der Außenseite einer verschachtelten Schleife ablegt.

Gibt es eine Erklärung für die Kardinalitätsschätzung von 1.0003 für SQL Server 2014, während SQL Server 2012 31.622 Zeilen schätzt? Gibt es eine gute Problemumgehung?

Hier ist eine kurze Reproduktion des Problems:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

Hier ist ein vollständigeres Skript, das zusätzliche Tests zeigt

Ich habe auch das Whitepaper zum SQL Server 2014 Cardinality Estimator gelesen , aber dort nichts gefunden, was die Situation verdeutlicht.

Geoff Patterson
quelle

Antworten:

20

Für das ältere CE sehe ich, dass die Schätzung für 3,16228% der Zeilen gilt - und das ist eine "magische Zahl" -Heuristik, die für Spalten-Literal-Prädikate verwendet wird (es gibt andere Heuristiken, die auf der Prädikatkonstruktion basieren -, aber die LENum die Spalte gewickelten Legacy-CE-Ergebnisse stimmen mit diesem Vermutungsrahmen überein. Beispiele hierfür finden Sie in einem Beitrag zu Selektivitätsschätzungen in Abwesenheit von Statistiken von Joe Sack und einer Konstant-Konstant-Vergleichsschätzung von Ian Jose.

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

Was nun das neue CE-Verhalten betrifft, sieht es so aus, als ob es jetzt für den Optimierer sichtbar ist (was bedeutet, dass wir Statistiken verwenden können). Ich habe die folgende Übung durchgearbeitet, um die Ausgabe des Taschenrechners zu betrachten, und Sie können die zugehörige automatische Generierung von Statistiken als Zeiger betrachten:

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

Leider basiert die Logik auf einer Schätzung der Anzahl unterschiedlicher Werte, die nicht an die Wirkung der LENFunktion angepasst ist .

Mögliche Problemumgehung

Sie können durch Umschreiben der eine Trie-basierte Schätzung unter beiden CE - Modelle erhalten LENals LIKE:

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

WIE Plan


Informationen zu den verwendeten Trace Flags:

  • 2363: Zeigt viele Informationen an, einschließlich der geladenen Statistiken.
  • 3604: druckt die Ausgabe von DBCC-Befehlen auf der Registerkarte Nachrichten.
Zane
quelle
13

Gibt es eine Erklärung für die Kardinalitätsschätzung von 1.0003 für SQL 2014, während SQL 2012 31.622 Zeilen schätzt?

Ich denke, @Zanes Antwort deckt diesen Teil ziemlich gut ab.

Gibt es eine gute Problemumgehung?

Sie können versuchen, eine nicht persistente berechnete Spalte für diese berechnete Spalte zu LEN(cust_nbr)erstellen und (optional) einen nicht gruppierten Index für diese berechnete Spalte zu erstellen. Das sollte Ihnen genaue Statistiken liefern.

Ich habe ein paar Tests gemacht und hier ist, was ich gefunden habe:

  • Statistiken wurden automatisch für die nicht persistierte berechnete Spalte erstellt, wenn kein Index dafür definiert wurde.
  • Das Hinzufügen des nicht gruppierten Index für die berechnete Spalte hat nicht nur nicht geholfen, sondern auch die Leistung etwas beeinträchtigt. Etwas höhere CPU und abgelaufene Zeiten. Etwas höhere geschätzte Kosten (was auch immer das wert ist).
  • Das Erstellen der berechneten Spalte als PERSISTED(kein Index) war besser als die beiden anderen Variationen. Geschätzte Zeilen waren genauer. CPU und verstrichene Zeit waren besser (wie erwartet, da pro Zeile nichts berechnet werden musste).
  • Ich konnte keinen gefilterten Index oder keine gefilterten Statistiken für die berechnete Spalte erstellen (da diese berechnet wird), auch wenn dies der PERSISTEDFall war :-(
Solomon Rutzky
quelle
1
Vielen Dank für den gründlichen Vergleich zwischen hartnäckig und nicht. Es ist gut zu wissen, dass, selbst wenn eine dauerhafte berechnete Spalte ihre Vorteile hat, eine dauerhafte ein sehr schneller Gewinn mit sehr geringem Aufwand sein kann, in einigen Fällen, in denen Statistiken zu einem Ausdruck von Vorteil sind.
Geoff Patterson