Stima della cardinalità per l'operatore LIKE (variabili locali)


24

Ho avuto l'impressione che quando si utilizza l' LIKEoperatore in tutti gli scenari di ottimizzazione per scenari sconosciuti sia il legacy che i nuovi CE utilizzano una stima del 9% (supponendo che siano disponibili statistiche pertinenti e l'ottimizzatore delle query non debba ricorrere a ipotesi di selettività).

Quando eseguo la query seguente sul database dei crediti ottengo stime diverse in base alle diverse CE. Con la nuova CE ricevo una stima di 900 righe che mi aspettavo, sotto la CE precedente ricevo una stima di 241.416 e non riesco a capire come questa stima è derivata. Qualcuno è in grado di far luce?

-- New CE (Estimate = 900)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName;

-- Forcing Legacy CE (Estimate = 241.416)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName
OPTION (
QUERYTRACEON 9481,
QUERYTRACEON 9292,
QUERYTRACEON 9204,
QUERYTRACEON 3604
);

Nel mio scenario, ho già impostato il database dei crediti sul livello di compatibilità 120, quindi perché nella seconda query sto usando i flag di traccia per forzare il CE legacy e anche per fornire informazioni su quali statistiche sono utilizzate / considerate dall'ottimizzatore delle query. Vedo che vengono utilizzate le statistiche di colonna su "lastname" ma non riesco ancora a capire come viene derivata la stima di 241.416.

Non sono riuscito a trovare online altro oltre a questo articolo di Itzik Ben-Gan , in cui si afferma "Quando si utilizza il predicato LIKE in tutti gli scenari di ottimizzazione per scenari sconosciuti, sia l'eredità che i nuovi CE utilizzano una stima del 9%". Le informazioni in quel post sembrerebbero errate.

Risposte:


28

L'ipotesi per LIKE nel tuo caso si basa su:

  • G: Lo standard 9% indovina ( sqllang!x_Selectivity_Like)
  • M: Un fattore 6 (numero magico)
  • D: Lunghezza media dei dati in byte (dalle statistiche), arrotondata per difetto all'intero

In particolare, sqllang!CCardUtilSQL7::ProbLikeGuessutilizza:

Selectivity (S) = G / M * LOG(D)

Gli appunti:

  • Il LOG(D)termine viene omesso se Dcompreso tra 1 e 2.
  • Se Dè inferiore a 1 (incluso per mancante o NULLstatistica):
    D = FLOOR(0.5 * maximum column byte length)

Questo tipo di stranezza e complessità è abbastanza tipico della CE originale.

Nell'esempio della domanda, la lunghezza media è 5 (5.6154 DBCC SHOW_STATISTICSarrotondato per difetto ):

Stima = 10.000 * (0,09 / 6 * LOG (5)) = 241,416

Altri valori di esempio:

 D   = Stima usando la formula per S
 15 = 406.208
 14 = 395.859
 13 = 384.742
 12 = 372.736
 11 = 359.684
 10 = 345.388
 09 = 329.584
 08 = 311.916
 07 = 291.887
 06 = 268.764
 05 = 241.416
 04 = 207.944
 03 = 164.792
 02 = 150.000 (LOG non utilizzato)
 01 = 150.000 (LOG non utilizzato)
 00 = 291.887 (LOG 7) / * FLOOR (0.5 * 15) [15 poiché il cognome è varchar (15)] * /

Banco prova

DECLARE
    @CharLength integer = 5, -- Set length here
    @Counter integer = 1;

CREATE TABLE #T (c1 varchar(15) NULL);

-- Add 10,000 rows
SET NOCOUNT ON;
SET STATISTICS XML OFF;

BEGIN TRANSACTION;
WHILE @Counter <= 10000
BEGIN
    INSERT #T (c1) VALUES (REPLICATE('X', @CharLength));
    SET @Counter = @Counter + 1;
END;
COMMIT TRANSACTION;

SET NOCOUNT OFF;
SET STATISTICS XML ON;

-- Test query
DECLARE @Like varchar(15);
SELECT * FROM #T AS T 
WHERE T.c1 LIKE @Like;

DROP TABLE #T;

15

Ho testato su SQL Server 2014 con il CE legacy e non ho nemmeno ottenuto il 9% come stima della cardinalità. Non sono riuscito a trovare nulla di preciso online, quindi ho fatto alcuni test e ho trovato un modello adatto a tutti i casi di test che ho provato, ma non posso essere sicuro che sia completo.

Nel modello che ho trovato, la stima deriva dal numero di righe nella tabella, dalla lunghezza media della chiave delle statistiche per la colonna filtrata e talvolta dalla lunghezza del tipo di dati della colonna filtrata. Esistono due diverse formule utilizzate per la stima.

Se FLOOR (lunghezza media della chiave) = 0, la formula di stima ignora le statistiche della colonna e crea una stima basata sulla lunghezza del tipo di dati. Ho provato solo con VARCHAR (N), quindi è possibile che ci sia una formula diversa per NVARCHAR (N). Ecco la formula per VARCHAR (N):

(stima riga) = (righe nella tabella) * (-0,004869 + 0,032649 * log10 (lunghezza del tipo di dati))

Questo ha una vestibilità molto bella, ma non è perfettamente preciso:

grafico della prima formula

L'asse x è la lunghezza del tipo di dati e l'asse y è il numero di righe stimate per una tabella con 1 milione di righe.

Query Optimizer userebbe questa formula se non avessi statistiche sulla colonna o se la colonna avesse abbastanza valori NULL per portare la lunghezza media della chiave al di sotto di 1.

Ad esempio, supponiamo di avere una tabella con 150k righe con filtro su un VARCHAR (50) e nessuna statistica di colonna. La previsione della stima delle righe è:

150000 * (-0,004869 + 0,032649 * log10 (50)) = 7590,1 righe

SQL per testarlo:

CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);

CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
    SELECT TOP (150000) 'ZZZZZ'
    FROM NUMS
    ORDER BY NUM;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;

SQL Server fornisce un conteggio di righe stimato di 7242.47, che è un po 'chiuso.

Se FLOOR (lunghezza media della chiave)> = 1, viene utilizzata una formula diversa basata sul valore di FLOOR (lunghezza media della chiave). Ecco una tabella di alcuni dei valori che ho provato:

1    1.5%
2    1.5%
3    1.64792%
4    2.07944%
5    2.41416%
6    2.68744%
7    2.91887%
8    3.11916%
9    3.29584%
10   3.45388%

Se FLOOR (lunghezza media della chiave) <6, utilizzare la tabella sopra. Altrimenti usa la seguente equazione:

(stima riga) = (righe nella tabella) * (-0.003381 + 0.034539 * log10 (FLOOR (lunghezza media della chiave)))

Questo ha una vestibilità migliore dell'altro, ma non è ancora perfettamente preciso.

grafico della seconda formula

L'asse x è la lunghezza media della chiave e l'asse y è il numero di righe stimate per una tabella con 1 milione di righe.

Per fare un altro esempio, supponiamo di avere una tabella con 10k righe con una lunghezza media della chiave di 5,5 per le statistiche sulla colonna filtrata. La stima delle righe sarebbe:

10000 * 0,241416 = 241,416 righe.

SQL per testarlo:

CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
    SELECT TOP (10000) 
    CASE 
      WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5) 
      ELSE REPLICATE('Z', 6)
    END
    FROM NUMS
    ORDER BY NUM;

CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING) 
WITH NORECOMPUTE, FULLSCAN;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;

La stima della riga è 241.416 che corrisponde a ciò che hai nella domanda. Ci sarebbe qualche errore se usassi un valore non nella tabella.

I modelli qui non sono perfetti ma penso che illustrino abbastanza bene il comportamento generale.

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.