La densità bizzarra si traduce in statistiche campionate


8

Un indice NC ottiene una distribuzione statistica totalmente diversa quando stimato con campionamento vs fullscan; quello campionato con un vettore di densità bizzarro. Ciò si traduce in piani di esecuzione scadenti.


Ho una tabella di ~ 27 M righe, con una colonna FK non nulla supportata da un indice non cluster. La tabella è raggruppata sulla sua chiave primaria. Entrambe le colonne sono varchar.

Un aggiornamento delle statistiche fullscan per la nostra colonna FK fornisce un vettore di densità dall'aspetto normale:

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

Cioè, dovremmo leggere circa 1,7 righe per ogni distinto a cui INSTANCELEMENTIDci stiamo unendo.

Un tipico cestino dell'istogramma è simile al seguente:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

Tuttavia, se eseguiamo un aggiornamento campionato (utilizzando il numero campione predefinito che è di 230k righe per questa tabella) le cose prendono una piega al bizzarro:

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

La densità INSTANCEELEMENTIDè ora maggiore di due ordini di grandezza . (La densità per entrambe le colonne è stata tuttavia stimata a un valore abbastanza accettabile).

Un tipico cestino dell'istogramma ora assomiglia a questo;

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

che è una distribuzione completamente diversa. Si noti che INSTANCEELEMENTIDcon il numero più alto di IDs associati ha 12, il numero più comune è 1. È anche molto strano che alcuni bin ottengano EQ_ROWS = 1, ciò accade a circa il 10% dei bin.

Non vi è alcun "sfortunato" disegno di strane file che potrebbero contribuire a questo.

Sto leggendo l'istogramma correttamente? Non sembra che il campionamento abbia in qualche modo ridimensionato EQ_ROWS, DISTINCT_RANGE_ROWS e AVG_RANGE_ROWS in modo errato?

Il tavolo è, per quanto posso dire, non distorto. Ho provato a emulare il campionatore stimando i valori con me stesso tablesample. Il conteggio di questi risultati in modo normale fornisce risultati in accordo con la versione fullscan, non con il campionatore.

Inoltre, non sono stato in grado di riprodurre questo comportamento su indici cluster.


Ho ridotto questo a questo per riprodurre:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

La tabella deve essere sufficientemente grande per essere osservata. L'ho visto con uniqueidentifere varchar(100)ma no int.

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

Confronta le due statistiche; quello con campionamento ora rappresenta un vettore di densità totalmente diversa e i contenitori dell'istogramma sono disattivati. Si noti che la tabella non è inclinata.

L'uso intcome tipo di dati non causa questo, SQL Server non esamina l'intero punto dati durante l'utilizzo varchar?

Vale la pena ricordare che il problema sembra ridimensionarsi, aumentando la frequenza di campionamento aiuta.

Risposte:


3

Ho riscontrato questo stesso problema di densità su alcuni degli indici non cluster sui database più grandi a cui ho accesso. Per prima cosa inizierò con alcune osservazioni che ho fatto sugli istogrammi e sui calcoli della densità:

  • SQL Server è in grado di utilizzare la chiave primaria nella tabella per dedurre qualcosa sulla densità di entrambe le colonne. Ciò significa che la densità che include le colonne PK sarà in genere molto accurata.
  • Il calcolo della densità per la prima colonna nelle statistiche è coerente con l'istogramma. Se l'istogramma non modella bene i dati, la densità potrebbe essere disattivata.
  • Per creare l'istogramma la StatManfunzione fa inferenze sui dati mancanti. Il comportamento può cambiare in base al tipo di dati della colonna.

Per un modo di esaminare il problema, supponiamo di campionare 100 righe da una tabella di 10000 righe e di ottenere 100 valori distinti. Un'ipotesi su quale sia il resto dei dati nella tabella è che ci sono 10000 valori univoci. Un'altra ipotesi è che ci sono 100 valori distinti ma ognuno di essi viene ripetuto 100 volte. La seconda ipotesi potrebbe sembrare irragionevole per te, con cui concordo. Tuttavia, come si bilanciano i due approcci quando i dati campionati tornano distribuiti in modo non uniforme? C'è una serie di algoritmi sviluppati per questo da Microsoft contenuti nella StatManfunzione. Gli algoritmi potrebbero non funzionare per tutte le interruzioni dei dati e tutti i livelli di campionamento.

Facciamo un esempio relativamente semplice. Userò VARCHARcolonne come nella tua tabella per vedere alcuni degli stessi comportamenti. Tuttavia, aggiungerò solo un valore distorto alla tabella. Sto testando con SQL Server 2016 SP1. Inizia con 100k righe con 100k valori univoci per la FKcolonna:

DROP TABLE IF EXISTS X_STATS_SMALL;

CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL, 
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);

CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

Ecco alcuni esempi dalle statistiche:

╔═════════════╦════════════════╦═════════╗
 All density  Average Length  Columns 
╠═════════════╬════════════════╬═════════╣
 1.00001E-05  4.888205        FK      
 1.00001E-05  9.77641         FK, ID  
╚═════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 1005          0           1        0                    1              
 10648         665.0898    1        664                  1.002173       
 10968         431.6008    1        432                  1              
 11182         290.0924    1        290                  1              
 1207          445.7517    1        446                  1              
 ...           ...         ...      ...                  ...            
 99989         318.3941    1        318                  1              
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

Per dati distribuiti uniformemente con un valore univoco per riga otteniamo una densità accurata, anche con una VARCHARcolonna dell'istogramma e una dimensione del campione di 14294 righe.

Ora aggiungiamo un valore distorto e aggiorniamo nuovamente le statistiche:

-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000',  REPLICATE('Z', 900)
FROM dbo.GetNums(70000);

UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

Con una dimensione del campione di 17010 righe, la densità della prima colonna è inferiore di quanto dovrebbe essere:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 6.811061E-05  4.935802        FK      
 5.882353E-06  10.28007        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS   DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
 10039         0           1         0                    1              
 10978         956.9945    1         138                  6.954391       
 11472         621.0283    1         89                   6.941863       
 1179          315.6046    1         46                   6.907561       
 11909         91.62713    1         14                   6.74198        
 ...           ...         ...       ...                  ...            
 35000         376.6893    69195.05  54                   6.918834       
 ...           ...         ...       ...                  ...            
 99966         325.7854    1         47                   6.909731       
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝

È sorprendente che AVG_RANGE_ROWSsia abbastanza uniforme per tutti i passaggi intorno a 6.9, anche per secchi di chiavi per i quali il campione non avrebbe potuto trovare valori duplicati. Non so perché questo sia. La spiegazione più probabile è che l'algoritmo utilizzato per indovinare le pagine mancanti non funziona bene con questa distribuzione dei dati e le dimensioni del campione.

Come affermato in precedenza, è possibile calcolare la densità per la colonna FK utilizzando l'istogramma. La somma dei DISTINCT_RANGE_ROWSvalori per tutti i passaggi è 14497. Ci sono 179 passaggi dell'istogramma, quindi la densità dovrebbe essere circa 1 / (179 + 14497) = 0,00006813845 che è abbastanza vicino al valore riportato.

I test con una tabella più grande possono mostrare come il problema può peggiorare man mano che la tabella diventa più grande. Questa volta inizieremo con 1 M righe:

DROP TABLE IF EXISTS X_STATS_LARGE;

CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);

CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

L'oggetto statistico non è ancora interessante. La densità per FKè 1.025289E-06 che è quasi esatta (1.0E-06).

Ora aggiungiamo un valore distorto e aggiorniamo nuovamente le statistiche:

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000',  REPLICATE('Z', 900)
FROM dbo.Getnums(700000);

UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

Con una dimensione del campione di 45627 righe, la densità della prima colonna è peggiore di prima:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 2.60051E-05   5.93563         FK      
 5.932542E-07  12.28485        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 100023        0           1        0                    1              
 107142        8008.354    1        306                  26.17787       
 110529        4361.357    1        168                  26.02392       
 114558        3722.193    1        143                  26.01217       
 116696        2556.658    1        98                   25.97568       
 ...           ...         ...      ...                  ...            
 350000        5000.522    700435   192                  26.03268       
 ...           ...         ...      ...                  ...            
 999956        2406.266    1        93                   25.96841       
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

AVG_RANGE_ROWSè fino a 26. È interessante notare che, se cambio la dimensione del campione in 170100 righe (10 volte l'altra tabella), il valore medio per AVG_RANGE_ROWSè di nuovo intorno a 6,9. Man mano che la tabella diventa più grande, SQL Server sceglierà una dimensione del campione più piccola, il che significa che deve fare ipotesi su una percentuale maggiore di pagine nella tabella. Questo può esagerare i problemi statistici per alcuni tipi di inclinazione dei dati.

In conclusione, è importante ricordare che SQL Server non calcola la densità in questo modo:

SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);

Quale per alcune distribuzioni di dati sarà molto accurato. Utilizza invece algoritmi non documentati . Nella tua domanda hai detto che i tuoi dati non erano distorti, ma il INSTANCEELEMENTIDvalore con il numero più alto di ID associati ha 12 e il numero più comune è 1. Ai fini degli algoritmi utilizzati da Statmanquello potrebbe essere distorto.

A quel punto non c'è nulla che tu possa fare al riguardo tranne che per raccogliere statistiche con una frequenza di campionamento più alta. Una strategia comune è quella di raccogliere statistiche con FULLSCANe NORECOMPUTE. Puoi aggiornare le statistiche con un lavoro su qualsiasi intervallo abbia senso per la tua velocità di modifica dei dati. Nella mia esperienza, un FULLSCANaggiornamento non è così male come la maggior parte della gente pensa, specialmente contro un indice. SQL Server può semplicemente eseguire la scansione dell'intero indice anziché dell'intera tabella (come farebbe per una tabella rowstore rispetto a una colonna non indicizzata). Inoltre, in SQL Serer 2014 FULLSCANvengono eseguiti in parallelo solo gli aggiornamenti delle statistiche, quindi un FULLSCANaggiornamento può terminare più rapidamente di alcuni aggiornamenti campionati.


Grazie per la risposta, Joe! Sembra un bug o un gap di funzionalità; ricordare che questo comportamento non si verifica quando si utilizzano valori basati su INT. Su INT il sistema funziona molto meglio e si ottiene una stima della distribuzione statistica che si avvicina molto meglio alla distribuzione reale. Mentre StatMan ovviamente fa un po 'di smoothing / euristica; Direi che è abbastanza sconcertante che tu possa ottenere risultati molto migliori te stesso calcolando direttamente l'istogramma, usando ancora gli stessi dati di origine che si otterrebbero contablesample

@JohanBenumEvensberget IMO non è così irragionevole che si comporti diversamente per le colonne INT. Con INT hai un dominio molto più limitato per i valori mancanti. Per le stringhe potrebbe davvero essere qualsiasi cosa fino al limite di lunghezza. Può essere sconcertante quando non otteniamo un buon istogramma ma funziona abbastanza bene per la maggior parte del tempo. Dato che il codice è segreto, non possiamo davvero dire se funziona come previsto o no. Potresti prendere in considerazione l'
idea di
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.