Ho intenzione di limitare questo post alla discussione delle statistiche a colonna singola perché sarà già piuttosto lungo e sei interessato a come SQL Server inserisce i dati in passaggi dell'istogramma. Per le statistiche a più colonne, l'istogramma viene creato solo sulla colonna principale.
Quando SQL Server stabilisce che è necessario un aggiornamento delle statistiche, avvia una query nascosta che legge tutti i dati di una tabella o un campione dei dati della tabella. È possibile visualizzare queste query con eventi estesi. Esiste una funzione chiamata StatMan
in SQL Server che è coinvolta nella creazione degli istogrammi. Per semplici oggetti statistici esistono almeno due diversi tipi di StatMan
query (esistono query diverse per aggiornamenti rapidi delle statistiche e sospetto che la funzionalità delle statistiche incrementali sulle tabelle partizionate utilizzi anche una query diversa).
Il primo prende semplicemente tutti i dati dalla tabella senza alcun filtro. Puoi vederlo quando il tavolo è molto piccolo o raccogli statistiche con l' FULLSCAN
opzione:
CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);
-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
SELECT TOP 100 PERCENT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
ORDER BY [SC0]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 16);
SQL Server seleziona la dimensione del campione automatico in base alla dimensione della tabella (penso che sia il numero di righe e pagine nella tabella). Se una tabella è troppo grande, la dimensione del campione automatico scende al di sotto del 100%. Ecco cosa ho ottenuto per la stessa tabella con righe 1M:
-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM
(
SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000]
FROM
(
SELECT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY [SC0], [SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);
TABLESAMPLE
è documentato ma StatMan e step_direction non lo sono. qui SQL Server campiona circa il 66,6% dei dati dalla tabella per creare l'istogramma. Ciò significa che potresti aggiornare un numero diverso di passaggi dell'istogramma durante l'aggiornamento delle statistiche (senza FULLSCAN
) sugli stessi dati. Non l'ho mai osservato in pratica, ma non vedo perché non sia possibile.
Eseguiamo alcuni test su dati semplici per vedere come cambiano le statistiche nel tempo. Di seguito è riportato un codice di test che ho scritto per inserire numeri interi sequenziali in una tabella, raccogliere statistiche dopo ogni inserimento e salvare le informazioni sulle statistiche in una tabella dei risultati. Cominciamo con l'inserimento di 1 riga alla volta fino a 10000. Letto di prova:
DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE X_STATS_RESULTS;
SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
SELECT @stats_id = stats_id FROM sys.stats
WHERE OBJECT_ID = @table_object_id
AND name = 'X_STATS_SEQ_INT_FULL';
SET @loop_num = 0;
WHILE @loop_num < @num_of_loops
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed
INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps
FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
END;
END;
Per questi dati il numero di passaggi dell'istogramma aumenta rapidamente a 200 (prima colpisce il numero massimo di passaggi con 397 righe), rimane a 199 o 200 fino a quando 1485 righe sono nella tabella, quindi diminuisce lentamente fino a quando l'istogramma ha solo 3 o 4 passi. Ecco un grafico di tutti i dati:
Ecco l'istogramma che appare per 10k righe:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 1 0 1
9999 9997 1 9997 1
10000 0 1 0 1
È un problema che l'istogramma abbia solo 3 passaggi? Sembra che le informazioni siano preservate dal nostro punto di vista. Si noti che poiché il tipo di dati è un INTEGER, possiamo capire quante righe sono presenti nella tabella per ogni numero intero compreso tra 1 e 10000. In genere anche SQL Server può capirlo, anche se ci sono alcuni casi in cui questo non funziona del tutto . Vedi questo post SE per un esempio di questo.
Cosa pensi che accadrà se eliminiamo una singola riga dalla tabella e aggiorniamo le statistiche? Idealmente, otterremmo un altro passaggio dell'istogramma per mostrare che il numero intero mancante non è più nella tabella.
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
È un po 'deludente. Se stessimo costruendo un istogramma a mano, aggiungeremmo un passaggio per ogni valore mancante. SQL Server utilizza un algoritmo di uso generale, quindi per alcuni set di dati potremmo essere in grado di elaborare un istogramma più adatto rispetto al codice che utilizza. Naturalmente, la differenza pratica tra ottenere 0 o 1 riga da una tabella è molto piccola. Ottengo gli stessi risultati quando collaudo con 20000 righe che ogni numero intero ha 2 righe nella tabella. L'istogramma non guadagna passi mentre cancello i dati.
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 2 0 1
9999 19994 2 9997 2
10000 0 2 0 1
Se provo con 1 milione di righe con ogni numero intero con 100 righe nella tabella ottengo risultati leggermente migliori, ma posso ancora costruire un istogramma migliore a mano.
truncate table X_SEQ_NUM;
BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps
Istogramma finale:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 100 0 1
998 99600 100 996 100
3983 298100 100 2981 100
9999 600900 100 6009 100
10000 0 100 0 1
Proviamo ulteriormente con numeri interi sequenziali ma con più righe nella tabella. Si noti che per le tabelle troppo piccole specificare manualmente una dimensione del campione non avrà alcun effetto, quindi aggiungerò 100 righe in ciascun inserto e raccoglierò statistiche ogni volta fino a 1 milione di righe. Vedo uno schema simile a prima, tranne quando raggiungo 637300 righe nella tabella non campiono più il 100% delle righe nella tabella con la frequenza di campionamento predefinita. Man mano che guadagno le righe aumenta il numero di passaggi dell'istogramma. Forse è perché SQL Server finisce con più lacune nei dati all'aumentare del numero di righe non campionate nella tabella. Non faccio 200 passi anche a 1 M di file, ma se continuassi ad aggiungere righe mi aspetto che ci arriverei e alla fine inizierei a scendere.
L'asse X è il numero di righe nella tabella. All'aumentare del numero di righe, le righe campionate variano leggermente e non superano i 650k.
Ora facciamo alcuni semplici test con i dati VARCHAR.
CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);
Qui sto inserendo 200 numeri (come stringhe) insieme a NULL.
INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps
Si noti che NULL ottiene sempre il proprio passaggio dell'istogramma quando viene trovato nella tabella. SQL Server avrebbe potuto fornirmi esattamente 201 passaggi per conservare tutte le informazioni, ma non è stato così. Tecnicamente le informazioni vanno perse perché '1111' ordina tra '1' e '2' per esempio.
Ora proviamo a inserire caratteri diversi anziché solo numeri interi:
truncate table X_SEQ_STR;
INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2
Nessuna vera differenza rispetto all'ultimo test.
Ora proviamo a inserire caratteri ma inserendo numeri diversi di ciascun personaggio nella tabella. Ad esempio, CHAR(11)
ha 1 riga, CHAR(12)
ha 2 righe, ecc.
truncate table X_SEQ_STR;
DECLARE
@loop_num INT;
BEGIN
SET NOCOUNT ON;
SET @loop_num = 0;
WHILE @loop_num < 200
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_STR WITH (TABLOCK)
SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
END;
END;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0
Come prima ancora non riesco a ottenere esattamente 200 passaggi dell'istogramma. Tuttavia, molti dei passaggi hanno RANGE_ROWS
0.
Per il test finale, inserirò una stringa casuale di 5 caratteri in ogni loop e raccoglierò le statistiche ogni volta. Ecco il codice la stringa casuale:
char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))
Ecco il grafico delle righe nei passaggi tabella vs istogramma:
Si noti che il numero di passaggi non scende al di sotto di 100 una volta che inizia a salire e scendere. Ho sentito da qualche parte (ma non riesco a cercarlo in questo momento) che l'algoritmo di creazione dell'istogramma di SQL Server combina i passaggi dell'istogramma quando si esaurisce lo spazio per loro. Quindi puoi finire con drastici cambiamenti nel numero di passaggi semplicemente aggiungendo un po 'di dati. Ecco un esempio dei dati che ho trovato interessanti:
ROWS_IN_TABLE ROWS_SAMPLED STEPS
36661 36661 133
36662 36662 143
36663 36663 143
36664 36664 141
36665 36665 138
Anche durante il campionamento FULLSCAN
, l'aggiunta di una singola riga può aumentare il numero di passaggi di 10, mantenerlo costante, quindi diminuirlo di 2, quindi diminuirlo di 3.
Cosa possiamo riassumere da tutto questo? Non posso provare nulla di tutto ciò, ma queste osservazioni sembrano essere vere:
- SQL Server utilizza un algoritmo di uso generale per creare gli istogrammi. Per alcune distribuzioni di dati potrebbe essere possibile creare manualmente una rappresentazione più completa dei dati.
- Se nella tabella sono presenti dati NULL e la query stats rileva che i dati NULL ottengono sempre il proprio passaggio dell'istogramma.
- Il valore minimo trovato nella tabella ottiene il proprio passo dell'istogramma con
RANGE_ROWS
= 0.
- Il valore massimo trovato nella tabella sarà il finale
RANGE_HI_KEY
nella tabella.
- Poiché SQL Server campiona più dati, potrebbe essere necessario combinare i passaggi esistenti per fare spazio ai nuovi dati che trova. Se si osservano sufficienti istogrammi, è possibile che vengano visualizzati valori comuni ripetere per
DISTINCT_RANGE_ROWS
o RANGE_ROWS
. Ad esempio, 255 si presenta un sacco di volte per RANGE_ROWS
e DISTINCT_RANGE_ROWS
per il caso di test finale qui.
- Per distribuzioni di dati semplici, è possibile che SQL Server combini i dati sequenziali in un passaggio dell'istogramma che non causa perdita di informazioni. Tuttavia, quando si aggiungono lacune ai dati, l'istogramma potrebbe non adattarsi come si spera.
Quando tutto questo è un problema? È un problema quando una query funziona male a causa di un istogramma che non è in grado di rappresentare la distribuzione dei dati in modo che l'ottimizzatore di query possa prendere buone decisioni. Penso che ci sia una tendenza a pensare che avere più passaggi dell'istogramma sia sempre migliore e che ci sia costernazione quando SQL Server genera un istogramma su milioni di righe o più ma non utilizza esattamente 200 o 201 passaggi dell'istogramma. Tuttavia, ho visto molti problemi con le statistiche anche quando l'istogramma ha 200 o 201 passaggi. Non abbiamo alcun controllo su quanti passaggi dell'istogramma generati da SQL Server per un oggetto statistico, quindi non mi preoccuperei. Tuttavia, ci sono alcuni passaggi che è possibile eseguire quando si verificano query con prestazioni scadenti causate da problemi di statistiche. Darò una panoramica estremamente breve.
La raccolta completa delle statistiche può aiutare in alcuni casi. Per tabelle molto grandi, la dimensione del campione automatico può essere inferiore all'1% delle righe della tabella. A volte ciò può portare a piani errati a seconda dell'interruzione dei dati nella colonna. La documentazione di Microsofts per CREATE STATISTICS e UPDATE STATISTICS afferma quanto segue:
SAMPLE è utile per casi speciali in cui il piano di query, basato sul campionamento predefinito, non è ottimale. Nella maggior parte dei casi, non è necessario specificare SAMPLE poiché Query Optimizer utilizza già il campionamento e determina la dimensione del campione statisticamente significativa per impostazione predefinita, come richiesto per creare piani di query di alta qualità.
Per la maggior parte dei carichi di lavoro, non è richiesta una scansione completa e il campionamento predefinito è adeguato. Tuttavia, alcuni carichi di lavoro che sono sensibili alle distribuzioni di dati molto variabili possono richiedere un aumento delle dimensioni del campione o persino una scansione completa.
In alcuni casi può essere utile creare statistiche filtrate. Potresti avere una colonna con dati distorti e molti valori distinti diversi. Se ci sono determinati valori nei dati che vengono comunemente filtrati, puoi creare un istogramma statistico solo per quei valori comuni. Query Optimizer può utilizzare le statistiche definite su un intervallo di dati più piccolo anziché le statistiche definite su tutti i valori di colonna. Non hai ancora la garanzia di ottenere 200 passaggi nell'istogramma, ma se crei le statistiche filtrate su un solo valore, un istogramma eseguirà tale passaggio.
L'uso di una vista partizionata è un modo per ottenere effettivamente più di 200 passaggi per una tabella. Supponiamo che tu possa facilmente dividere una grande tabella in una tabella all'anno. Si crea una UNION ALL
vista che combina tutte le tabelle annuali. Ogni tabella avrà il proprio istogramma. Si noti che le nuove statistiche incrementali introdotte in SQL Server 2014 consentono solo di rendere più efficienti gli aggiornamenti delle statistiche. Query Optimizer non utilizzerà le statistiche create per partizione.
Ci sono molti altri test che potrebbero essere eseguiti qui, quindi ti incoraggio a sperimentare. Ho fatto questo test su SQL Server 2014 Express, quindi davvero non c'è niente che ti fermi.