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
StatMan
funzione 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 StatMan
funzione. Gli algoritmi potrebbero non funzionare per tutte le interruzioni dei dati e tutti i livelli di campionamento.
Facciamo un esempio relativamente semplice. Userò VARCHAR
colonne 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 FK
colonna:
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 VARCHAR
colonna 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_ROWS
sia 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_ROWS
valori 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 INSTANCEELEMENTID
valore con il numero più alto di ID associati ha 12 e il numero più comune è 1. Ai fini degli algoritmi utilizzati da Statman
quello 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 FULLSCAN
e 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 FULLSCAN
aggiornamento 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 FULLSCAN
vengono eseguiti in parallelo solo gli aggiornamenti delle statistiche, quindi un FULLSCAN
aggiornamento può terminare più rapidamente di alcuni aggiornamenti campionati.
tablesample