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 bigint
table de base ( ), partition_id
par 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 REPEATABLE
clause
- Pour les échantillons
UPDATE STATISTICS
, la REPEATABLE
valeur 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 REPEATABLE
valeur 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 41A7
hex (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 StatMan
requê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 REPEATABLE
valeur. La REPEATABLE
valeur 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 TABLESAMPLE
clause (même zéro pour cent).