Perché LEN () funziona sottovalutando gravemente la cardinalità in SQL Server 2014?


26

Ho una tabella con una colonna stringa e un predicato che controlla le righe con una certa lunghezza. In SQL Server 2014, vedo una stima di 1 riga indipendentemente dalla lunghezza che sto verificando. Ciò sta producendo piani molto scadenti perché in realtà ci sono migliaia o addirittura milioni di righe e SQL Server sta scegliendo di posizionare questa tabella sul lato esterno di un ciclo nidificato.

Esiste una spiegazione per la stima della cardinalità di 1.0003 per SQL Server 2014 mentre SQL Server 2012 stima 31.622 righe? C'è una buona soluzione?

Ecco una breve riproduzione del problema:

-- 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

Ecco uno script più completo che mostra ulteriori test

Ho anche letto il white paper sullo stimatore di cardinalità di SQL Server 2014 , ma non ho trovato nulla che chiarisse la situazione.

Risposte:


20

Per l'eredità CE, vedo che la stima è per il 3,16228% delle righe - e questo è un euristico "numero magico" usato per colonna = predicati letterali (ci sono altre euristiche basate sulla costruzione del predicato - ma il LENgiro attorno alla colonna per il i risultati CE legacy corrispondono a questo quadro ipotesi). Puoi vedere esempi di questo in un post su Selettività Guess in assenza di statistiche di Joe Sack e Stima di confronto costante-costante di Ian Jose.

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

Ora per quanto riguarda il nuovo comportamento CE, sembra che questo sia ora visibile all'ottimizzatore (il che significa che possiamo usare le statistiche). Ho seguito l'esercizio di visualizzazione dell'output della calcolatrice di seguito e puoi guardare la generazione automatica di statistiche associata come un puntatore:

-- 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

Sfortunatamente la logica si basa su una stima del numero di valori distinti, che non è aggiustato per l'effetto della LENfunzione.

Possibile soluzione alternativa

È possibile ottenere una stima basata su trie in entrambi i modelli CE riscrivendo LENcome LIKE:

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

COME piano


Informazioni sulle bandiere di traccia utilizzate:

  • 2363: mostra molte informazioni, incluse le statistiche caricate.
  • 3604: stampa l'output dei comandi DBCC nella scheda messaggi.

13

Esiste una spiegazione per la stima della cardinalità di 1.0003 per SQL 2014 mentre SQL 2012 stima 31.622 righe?

Penso che la risposta di @ Zane copra abbastanza bene questa parte.

C'è una buona soluzione?

Puoi provare a creare una colonna calcolata non persistente per LEN(cust_nbr) e (facoltativamente) creare un indice non cluster su quella colonna calcolata. Questo dovrebbe darti statistiche accurate.

Ho fatto alcuni test ed ecco cosa ho trovato:

  • Le statistiche sono state create automaticamente sulla colonna calcolata non persistente, quando non è stato definito alcun indice su di essa.
  • L'aggiunta dell'indice non cluster alla colonna calcolata non solo non ha aiutato, ma ha danneggiato un po 'le prestazioni. CPU leggermente superiore e tempi trascorsi. Costo stimato leggermente più alto (qualunque cosa valga la pena).
  • Rendere la colonna calcolata come PERSISTED(nessun indice) era meglio delle altre due varianti. Le righe stimate erano più precise. La CPU e il tempo trascorso erano migliori (come previsto poiché non doveva calcolare nulla per riga).
  • Non sono stato in grado di creare un indice filtrato o statistiche filtrate sulla colonna calcolata (a causa del suo calcolo), anche se fosse PERSISTED:-(

1
Grazie per il confronto approfondito tra persistente e non. È bene sapere che anche se la colonna calcolata persistente ha i suoi vantaggi, la non-persistenza può essere una vittoria molto rapida con un sovraccarico molto piccolo in alcuni casi in cui le statistiche su un'espressione sono utili.
Geoff Patterson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.