Fondo
Los datos para el objeto de estadísticas se recopilan mediante una declaración del formulario:
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)
Puede recopilar esta declaración con Extended Events o Profiler ( SP:StmtCompleted
).
Las consultas de generación de estadísticas a menudo acceden a la tabla base (en lugar de a un índice no agrupado) para evitar la agrupación de valores que ocurre naturalmente en las páginas de índice no agrupadas.
El número de filas muestreadas depende del número de páginas enteras seleccionadas para el muestreo. Cada página de la tabla está seleccionada o no. Todas las filas en las páginas seleccionadas contribuyen a las estadísticas.
Números al azar
SQL Server usa un generador de números aleatorios para decidir si una página califica o no. El generador utilizado en este caso es el generador de números aleatorios de Lehmer con valores de parámetros como se muestra a continuación:
X siguiente = X semilla * 7 5 mod (2 31 - 1)
El valor de se calcula como la suma de:Xseed
La parte entera baja de la bigint
tabla base ( ), partition_id
p . Ej.
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;
El valor especificado en la REPEATABLE
cláusula.
- Para la muestra
UPDATE STATISTICS
, el REPEATABLE
valor es 1.
- Este valor se expone en el
m_randomSeed
elemento de la información de depuración interna del método de acceso que se muestra en los planes de ejecución cuando el indicador de seguimiento 8666 está habilitado, por ejemplo<Field FieldName="m_randomSeed" FieldValue="1" />
Para SQL Server 2012, este cálculo ocurre en sqlmin!UnOrderPageScanner::StartScan
:
mov edx,dword ptr [rcx+30h]
add edx,dword ptr [rcx+2Ch]
donde memoria en [rcx+30h]
contiene los 32 bits bajos de la identificación de partición y memoria en [rcx+2Ch]
contiene el REPEATABLE
valor en uso.
El generador de números aleatorios se inicializa más tarde en el mismo método, llamando sqlmin!RandomNumGenerator::Init
, donde la instrucción:
imul r9d,r9d,41A7h
... multiplica la semilla por 41A7
hexadecimal (16807 decimal = 7 5 ) como se muestra en la ecuación anterior.
Los números aleatorios posteriores (para páginas individuales) se generan utilizando el mismo código básico incluido sqlmin!UnOrderPageScanner::SetupSubScanner
.
StatMan
Para la StatMan
consulta de ejemplo que se muestra arriba, se recopilarán las mismas páginas que para la instrucción 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
Esto coincidirá con la salida 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';
Caso extremo
Una consecuencia del uso del generador de números aleatorios MINSTD Lehmer es que los valores semilla cero e int.max no deben usarse, ya que esto dará como resultado que el algoritmo produzca una secuencia de ceros (seleccionando cada página).
El código detecta cero y utiliza un valor del 'reloj' del sistema como semilla en ese caso. No hace lo mismo si la semilla es int.max ( 0x7FFFFFFF
= 2 31 - 1).
Podemos diseñar este escenario ya que la semilla inicial se calcula como la suma de los 32 bits bajos de la identificación de la partición y el REPEATABLE
valor. El REPEATABLE
valor que dará como resultado que la semilla sea int.max y, por lo tanto, cada página que se seleccione para la muestra es:
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;
Trabajando eso en un ejemplo 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);
Eso seleccionará cada fila en cada página, independientemente de lo TABLESAMPLE
que diga la cláusula (incluso cero por ciento).