Comportement étrange avec des tailles d'échantillons pour les mises à jour des statistiques

25

J'ai joué sur les seuils d'échantillonnage avec des mises à jour de statistiques sur SQL Server (2012) et j'ai remarqué un comportement curieux. Fondamentalement, le nombre de lignes échantillonnées semble varier dans certaines circonstances - même avec le même ensemble de données.

J'exécute cette requête:

--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;

Lorsque je regarde la sortie de SHOW_STATISTICS, je constate que les "lignes échantillonnées" varient à chaque exécution complète (c'est-à-dire que la table est supprimée, recréée et repeuplée).

Par exemple:

Lignes échantillonnées

  • 318618
  • 319240
  • 324198
  • 314154

Je m'attendais à ce que ce chiffre soit le même à chaque fois que le tableau est identique. Soit dit en passant, je n'obtiens pas ce comportement si je supprime simplement les données et les réinsère.

Ce n'est pas une question critique, mais je serais intéressé à comprendre ce qui se passe.

Matthew McGiffen
la source
2
Combien de fichiers dans le groupe de fichiers dans lequel vous insérez? J'ai essayé plusieurs fois en 2016 et les deux fois, le tableau a été divisé en 3584 pages avec 279 lignes et 1 avec 64. Les deux tailles d'échantillon différentes que j'ai vues étaient 314712 et 315270 - les deux sont des multiples exacts de 279.
Martin Smith
1
@JoeObbish - Il lit toujours des pages entières AFAIK donc je n'ai pas été surpris par cela. Pour une raison quelconque, je pensais que les chiffres de la question ne correspondaient pas à ce modèle. Mais après avoir refait les maths qu'ils font. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279Et 314154=1126donc la variance est le nombre de pages de l' échantillon.
Martin Smith
@MartinSmithJuste le seul fichier - le chiffre 279 est intéressant, j'aime toujours comprendre les modèles impliqués
Matthew McGiffen

Réponses:

26

Contexte

Les données de l'objet statistiques sont collectées à l'aide d'une instruction de la forme:

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)

Vous pouvez collecter cette instruction avec Extended Events ou Profiler ( SP:StmtCompleted).

Les requêtes de génération de statistiques accèdent souvent à la table de base (plutôt qu'à un index non cluster) pour éviter le regroupement de valeurs qui se produit naturellement sur les pages d'index non cluster.

Le nombre de lignes échantillonnées dépend du nombre de pages entières sélectionnées pour l'échantillonnage. Chaque page du tableau est sélectionnée ou ne l'est pas. Toutes les lignes des pages sélectionnées contribuent aux statistiques.

Nombres aléatoires

SQL Server utilise un générateur de nombres aléatoires pour décider si une page est admissible ou non. Le générateur utilisé dans ce cas est le générateur de nombres aléatoires Lehmer avec des valeurs de paramètres comme indiqué ci-dessous:

X suivant = X graine * 7 5 mod (2 31 - 1)

La valeur de est calculée comme la somme de:Xseed

  • La partie entière basse de la biginttable de base ( ), partition_idpar exemple

    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;
  • La valeur spécifiée dans la REPEATABLEclause

    • Pour les échantillons UPDATE STATISTICS, la REPEATABLEvaleur est 1.
    • Cette valeur est exposée dans l' m_randomSeedélément des informations de débogage internes de la méthode d'accès affichées dans les plans d'exécution lorsque l'indicateur de trace 8666 est activé, par exemple<Field FieldName="m_randomSeed" FieldValue="1" />

Pour SQL Server 2012, ce calcul se produit dans sqlmin!UnOrderPageScanner::StartScan:

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

où la mémoire à [rcx+30h]contient les 32 bits de poids faible de l'ID de partition et la mémoire à [rcx+2Ch]contient la REPEATABLEvaleur utilisée.

Le générateur de nombres aléatoires est initialisé plus tard dans la même méthode, appelant sqlmin!RandomNumGenerator::Init, où l'instruction:

imul    r9d,r9d,41A7h

... multiplie la graine par 41A7hex (16807 décimal = 7 5 ) comme indiqué dans l'équation ci-dessus.

Les nombres aléatoires ultérieurs (pour les pages individuelles) sont générés à l'aide du même code de base intégré à sqlmin!UnOrderPageScanner::SetupSubScanner.

StatMan

Pour l'exemple de StatManrequête ci-dessus, les mêmes pages seront collectées que pour l'instruction 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

Cela correspondra à la sortie de:

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';

Cas de bord

L'une des conséquences de l'utilisation du générateur de nombres aléatoires MINSTD Lehmer est que les valeurs de départ zéro et int.max ne doivent pas être utilisées car cela entraînera la production par l'algorithme d'une séquence de zéros (sélection de chaque page).

Le code détecte zéro et utilise une valeur de l '«horloge» du système comme valeur de départ dans ce cas. Il n'en va pas de même si la valeur de départ est int.max ( 0x7FFFFFFF= 2 31 - 1).

Nous pouvons concevoir ce scénario car la graine initiale est calculée comme la somme des 32 bits bas de l'id de partition et de la REPEATABLEvaleur. La REPEATABLEvaleur qui se traduira par la graine étant int.max et donc chaque page sélectionnée pour l'échantillon est:

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;

En travaillant cela dans un exemple complet:

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);

Cela sélectionnera chaque ligne de chaque page, quelle que soit la TABLESAMPLEclause (même zéro pour cent).

Paul White dit GoFundMonica
la source
11

Ceci est une excellente question! Je vais commencer par ce que je sais avec certitude, puis passer à la spéculation. Beaucoup de détails à ce sujet dans mon article de blog ici .

Des mises à jour de statistiques échantillonnées sont utilisées TABLESAMPLEdans les coulisses. Il est assez facile de trouver de la documentation en ligne à ce sujet. Cependant, je pense qu'il n'est pas bien connu que les lignes renvoyées par TABLESAMPLEdépendent en partie hobt_idde l'objet. Lorsque vous déposez et recréez l'objet, vous obtenez un nouveau hobt_idafin que les lignes renvoyées par échantillonnage aléatoire soient différentes.

Si vous supprimez et réinsérez les données, elles hobt_idrestent inchangées. Tant que les données sont disposées de la même manière sur le disque (une analyse de l'ordre d'allocation renvoie les mêmes résultats dans le même ordre), les données échantillonnées ne doivent pas changer.

Vous pouvez également modifier le nombre de lignes échantillonnées en reconstruisant l'index cluster sur la table. Par exemple:

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

Quant à savoir pourquoi cela se produit, je pense que c'est parce que SQL Server analyse l'index clusterisé au lieu de l'index non cluster lors de la collecte des statistiques échantillonnées sur un index. Je pense également qu'il y a une valeur cachée (pour ceux d'entre nous qui tracent les requêtes de mise à jour des statistiques cachées) à REPEATABLEutiliser avec TABLESAMPLE. Je n'ai rien prouvé, mais cela explique pourquoi votre histogramme et les lignes échantillonnées changent avec une reconstruction de l'index clusterisé.

Joe Obbish
la source