Strano comportamento con dimensioni del campione per gli aggiornamenti delle statistiche


25

Ho studiato le soglie di campionamento con gli aggiornamenti delle statistiche su SQL Server (2012) e ho notato alcuni comportamenti curiosi. Fondamentalmente il numero di righe campionate sembra variare in alcune circostanze, anche con lo stesso set di dati.

Eseguo questa query:

--Drop table if exists
IF (OBJECT_ID('dbo.Test')) IS NOT NULL DROP TABLE dbo.Test;

--Create Table for Testing
CREATE TABLE dbo.Test(Id INT IDENTITY(1,1) CONSTRAINT PK_Test PRIMARY KEY CLUSTERED, TextValue VARCHAR(20) NULL);

--Insert enough data so we have more than 8Mb (the threshold at which sampling kicks in)
INSERT INTO dbo.Test(TextValue) 
SELECT TOP 1000000 'blahblahblah'
FROM sys.objects a, sys.objects b, sys.objects c, sys.objects d;  

--Create Index on TextValue
CREATE INDEX IX_Test_TextValue ON dbo.Test(TextValue);

--Update Statistics without specifying how many rows to sample
UPDATE STATISTICS dbo.Test IX_Test_TextValue;

--View the Statistics
DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER;

Quando guardo l'output di SHOW_STATISTICS, scopro che "Rows Sampled" varia con ogni esecuzione completa (ovvero la tabella viene eliminata, ricreata e ripopolata).

Per esempio:

Righe campionate

  • 318.618
  • 319.240
  • 324198
  • 314154

La mia aspettativa era che questa cifra fosse la stessa ogni volta che la tabella fosse identica. A proposito, non ottengo questo comportamento se cancello semplicemente i dati e li reinserisco.

Non è una domanda critica, ma sarei interessato a capire cosa sta succedendo.


2
Quanti file nel filegroup si stanno inserendo? Ho provato un paio di volte nel 2016 ed entrambe le volte la tabella è stata divisa in 3584 pagine con 279 righe e 1 con 64. Le due diverse dimensioni del campione che ho visto erano 314712 e 315270 - entrambi i multipli esatti di 279.
Martin Smith

1
@JoeObbish - Legge sempre intere pagine AFAIK, quindi non mi ha sorpreso. Per qualche ragione, ho pensato che i numeri nella domanda non corrispondessero a quel modello. Ma dopo aver rifatto la matematica che fanno. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279E 314154=1126quindi la varianza è il numero di pagine campione.
Martin Smith,

@MartinSmithSolo un file - la cifra 279 è interessante, mi piace sempre capire gli schemi coinvolti
Matthew McGiffen,

Risposte:


26

sfondo

I dati per l'oggetto statistico vengono raccolti usando un'istruzione del modulo:

SELECT 
    StatMan([SC0], [SC1], [SB0000]) 
FROM 
(
    SELECT TOP 100 PERCENT 
        [SC0], [SC1], STEP_DIRECTION([SC0]) OVER (ORDER BY NULL) AS [SB0000]
    FROM 
    (
        SELECT 
            [TextValue] AS [SC0], 
            [Id] AS [SC1] 
        FROM [dbo].[Test] 
            TABLESAMPLE SYSTEM (2.223684e+001 PERCENT) 
            WITH (READUNCOMMITTED) 
    ) AS _MS_UPDSTATS_TBL_HELPER 
    ORDER BY 
        [SC0], 
        [SC1], 
        [SB0000] 
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1)

È possibile raccogliere questa affermazione con Eventi estesi o Profiler ( SP:StmtCompleted).

Le query di generazione delle statistiche spesso accedono alla tabella di base (anziché a un indice non cluster) per evitare il raggruppamento di valori che si verifica naturalmente nelle pagine dell'indice non cluster.

Il numero di righe campionate dipende dal numero di pagine intere selezionate per il campionamento. Ogni pagina della tabella è selezionata o non lo è. Tutte le righe delle pagine selezionate contribuiscono alle statistiche.

Numeri casuali

SQL Server utilizza un generatore di numeri casuali per decidere se una pagina è idonea o meno. Il generatore utilizzato in questo caso è il generatore di numeri casuali Lehmer con valori di parametro come mostrato di seguito:

X successivo = X seme * 7 5 mod (2 31 - 1)

Il valore di viene calcolato come la somma di:Xseed

  • La parte intera bassa della biginttabella di base ( ) partition_idad es

    SELECT
        P.[partition_id] & 0xFFFFFFFF
    FROM sys.partitions AS P
    WHERE
        P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
        AND P.index_id = 1;
  • Il valore specificato nella REPEATABLEclausola

    • Per il campione UPDATE STATISTICS, il REPEATABLEvalore è 1.
    • Questo valore è esposto m_randomSeednell'elemento delle informazioni di debug interne del metodo di accesso mostrate nei piani di esecuzione quando è abilitato il flag di traccia 8666, ad esempio<Field FieldName="m_randomSeed" FieldValue="1" />

Per SQL Server 2012, questo calcolo si verifica in sqlmin!UnOrderPageScanner::StartScan:

mov     edx,dword ptr [rcx+30h]
add     edx,dword ptr [rcx+2Ch]

dove memory at [rcx+30h]contiene i 32 bit bassi dell'ID partizione e memory at [rcx+2Ch]contiene il REPEATABLEvalore in uso.

Il generatore di numeri casuali viene inizializzato successivamente nello stesso metodo, chiamando sqlmin!RandomNumGenerator::Init, dove l'istruzione:

imul    r9d,r9d,41A7h

... moltiplica il seme per 41A7esadecimale (16807 decimale = 7 5 ) come mostrato nell'equazione sopra.

I numeri casuali successivi (per le singole pagine) vengono generati utilizzando lo stesso codice di base incorporato sqlmin!UnOrderPageScanner::SetupSubScanner.

Statman

Per la StatManquery di esempio mostrata sopra, verranno raccolte le stesse pagine dell'istruzione T-SQL:

SELECT 
    COUNT_BIG(*) 
FROM dbo.Test AS T 
    TABLESAMPLE SYSTEM (2.223684e+001 PERCENT)  -- Same sample %
    REPEATABLE (1)                              -- Always 1 for statman
    WITH (INDEX(0));                            -- Scan base object

Questo corrisponderà all'output di:

SELECT 
    DDSP.rows_sampled
FROM sys.stats AS S
CROSS APPLY sys.dm_db_stats_properties(S.[object_id], S.stats_id) AS DDSP
WHERE 
    S.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
    AND S.[name] = N'IX_Test_TextValue';

Custodia per bordi

Una conseguenza dell'utilizzo del generatore di numeri casuali MINSTD Lehmer è che i valori di seed zero e int.max non devono essere utilizzati in quanto ciò comporterebbe l'algoritmo che produce una sequenza di zero (selezionando ogni pagina).

Il codice rileva zero e utilizza un valore dall'orologio di sistema come seme in quel caso. Non fa lo stesso se il seme è int.max ( 0x7FFFFFFF= 2 31 - 1).

Possiamo progettare questo scenario poiché il seed iniziale viene calcolato come la somma dei 32 bit bassi dell'ID partizione e del REPEATABLEvalore. Il REPEATABLEvalore che determinerà che il seme sia int.max e quindi ogni pagina selezionata per il campione è:

SELECT
    0x7FFFFFFF - (P.[partition_id] & 0xFFFFFFFF)
FROM sys.partitions AS P
WHERE
    P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
    AND P.index_id = 1;

Elaborandolo in un esempio completo:

DECLARE @SQL nvarchar(4000) = 
    N'
    SELECT
        COUNT_BIG(*) 
    FROM dbo.Test AS T 
        TABLESAMPLE (0 PERCENT) 
        REPEATABLE (' +
        (
            SELECT TOP (1)
                CONVERT(nvarchar(11), 0x7FFFFFFF - P.[partition_id] & 0xFFFFFFFF)
            FROM sys.partitions AS P
            WHERE
                P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
                AND P.index_id = 1
        ) + ')
        WITH (INDEX(0));';

PRINT @SQL;
--EXECUTE (@SQL);

Che selezionerà ogni riga su ogni pagina qualunque sia la TABLESAMPLEclausola dice (anche zero per cento).


11

Questa è un'ottima domanda! Inizierò con quello che so per certo e poi passerò alla speculazione. Molti dettagli su questo nel mio post sul blog qui .

Gli aggiornamenti delle statistiche campionate vengono utilizzati TABLESAMPLEdietro le quinte. È abbastanza facile trovare documentazione al riguardo online. Tuttavia, credo che non sia noto che le righe restituite TABLESAMPLEdipendono in hobt_idparte dall'oggetto. Quando si rilascia e si ricrea l'oggetto si ottiene un nuovo, hobt_idquindi le righe restituite dal campionamento casuale sono diverse.

Se si eliminano e si reinseriscono i dati, hobt_idrimangono invariati. Finché i dati sono disposti allo stesso modo sul disco (una scansione dell'ordine di allocazione restituisce gli stessi risultati nello stesso ordine), i dati campionati non devono cambiare.

È inoltre possibile modificare il numero di righe campionate ricostruendo l'indice cluster sulla tabella. Per esempio:

UPDATE STATISTICS dbo.Test IX_Test_TextValue;

DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273862 rows

ALTER INDEX PK_Test on Test REBUILD;

UPDATE STATISTICS dbo.Test IX_Test_TextValue;

DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273320 rows

Per quanto riguarda il motivo per cui ciò accade, credo che sia perché SQL Server esegue la scansione dell'indice cluster anziché dell'indice non cluster durante la raccolta di statistiche campionate su un indice. Penso anche che ci sia un valore nascosto (per quelli di noi che stanno tracciando le query di aggiornamento delle statistiche nascoste) da REPEATABLEusare con TABLESAMPLE. Non ho dimostrato nulla di tutto ciò, ma spiega perché il tuo istogramma e le righe campionate cambiano con una ricostruzione dell'indice cluster.


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.