Modifiche alle stime sui predicati che contengono SUBSTRING () in SQL Server 2016?


13

Esiste documentazione o ricerca sulle modifiche in SQL Server 2016 su come viene stimata la cardinalità per predicati contenenti SUBSTRING () o altre funzioni stringa?

Il motivo per cui sto chiedendo è che stavo esaminando una query le cui prestazioni sono diminuite nella modalità di compatibilità 130 e il motivo è stato correlato a una modifica nella stima del numero di righe che corrispondono a una clausola WHERE che conteneva una chiamata a SUBSTRING (). Ho corretto il problema con una riscrittura delle query, ma mi chiedo se qualcuno è a conoscenza di documentazione sulle modifiche in quest'area in SQL Server 2016.

Il codice demo è sotto. Le stime sono molto vicine in questo caso di test, ma l'accuratezza varia a seconda dei dati.

Nel caso di test, nel livello 120 di compatibilità, SQL Server sembra utilizzare l'istogramma per la stima, mentre nel livello di compatibilità 130 SQL sembra assumere un 10% fisso delle corrispondenze di tabella.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/

1
Non sei sicuro della domanda particolare, ma se le Y5_EG3stringhe sono solo codici e sempre in maiuscolo, puoi sempre provare a specificare un confronto binario Latin1_General_100_BIN2- che dovrebbe migliorare la velocità sulle operazioni di filtro. Basta aggiungere COLLATE Latin1_General_100_BIN2alla CREATE TABLEdichiarazione, subito dopo il varchar(15). Sarei curioso di vedere se influiva anche sulla generazione / stima del piano.
Solomon Rutzky,

Risposte:


8

Non sono a conoscenza di alcuna documentazione. Ho esaminato questo aspetto e fatto alcune osservazioni che sono troppo lunghe per un commento.

La stima del 10% non è sempre un degrado. Prendi il seguente esempio.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

e la WHEREclausola nella tua domanda.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

La tabella contiene un milione di righe. Tutti corrispondono al predicato. Sotto il livello di compatibilità 130 l'ipotesi del 10% produce una stima di 100.000. Sotto 120 le righe stimate sono 1.03913.

Il comportamento 120 utilizza l'istogramma ma solo per ottenere il numero di righe distinte. Il vettore di densità nel mio caso mostra 1.039131E-06 e questo viene moltiplicato per la cardinalità della tabella per ottenere il conteggio delle righe stimato. Tutti i valori sono in realtà diversi ma tutti corrispondono al predicato.

Tracciare l' query_optimizer_estimate_cardinalityevento esteso mostra che sotto 130 ci sono due <StatsCollection Name="CStCollFilter"eventi diversi . Il primo stima 100.000. Il secondo carica l'istogramma e utilizza CSelCalcPointPredsFreqBased / DistinctCountCalculator per ottenere la stima 1.04. Questo secondo risultato appare inutilizzato.

Il comportamento che hai osservato non viene applicato in modo coerente nel 130. Ho aggiunto che mi ORDER BY TheStringaspettavo che ciò rappresentasse una chiara vittoria per lo stimatore 130 poiché i 120 lottano con una concessione di memoria per una riga, ma questa piccola modifica è stata sufficiente per portare le righe stimate a 1.03913 anche nel caso 130.

L'aggiunta OPTION (QUERYRULEOFF SelectToFilter)ripristina la stima andando nell'ordinamento a 100.000 ma la concessione di memoria non aumenta e le stime che escono dall'ordinamento si basano ancora sui valori distinti della tabella.

inserisci qui la descrizione dell'immagine

Allo stesso modo modificare la soglia di costo per il parallelismo in modo che la query ottenga un piano parallelo era sufficiente nel caso 130 per tornare alla stima inferiore. L'aggiunta QUERYTRACEON 8757provoca anche la stima più bassa. Sembra che la stima del 10% sia mantenuta solo per piani banali.

La tua riscrittura proposta con

WHERE TheString LIKE 'ZZ[_]%'

Mostra stime molto superiori a entrambi. L'output per questo è

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Dimostrando che ha usato i tentativi . Maggiori informazioni al riguardo sono disponibili nella sezione delle statistiche di riepilogo delle stringhe qui sopra .

Tuttavia, non è uguale alla query originale. Poiché la prima istanza di _è ora considerata essere sempre il terzo personaggio anziché essere trovato in modo dinamico.

Se questo presupposto è codificato nella query originale

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

Il metodo di stima cambia CSelCalcHistogramComparison(INTERVAL)e le righe stimate diventano accurate.

È in grado di convertirlo in un intervallo

WHERE TheString >=  'ZZ_' AND TheString < ???

e usa l'istogramma per stimare il numero di righe con valori in quell'intervallo.

Questo vale solo per la stima della cardinalità. LIKEè preferibile in quanto può utilizzare un intervallo di ricerca in fase di esecuzione. SUBSTRING(TheString, 1, 3)o LEFT(TheString, 3)no.

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.