Comportamiento extraño con tamaños de muestra para actualizaciones estadísticas

25

He estado investigando los umbrales de muestreo con actualizaciones estadísticas en SQL Server (2012) y noté un comportamiento curioso. Básicamente, el número de filas muestreadas parece variar en algunas circunstancias, incluso con el mismo conjunto de datos.

Ejecuto esta consulta:

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

Cuando miro el resultado de SHOW_STATISTICS, descubro que las "Filas muestreadas" varían con cada ejecución completa (es decir, la tabla se descarta, se recrea y se vuelve a llenar).

Por ejemplo:

Filas muestreadas

  • 318618
  • 319240
  • 324198
  • 314154

Mi expectativa era que esta cifra sería la misma cada vez que la tabla es idéntica. Por cierto, no obtengo este comportamiento si solo elimino los datos y los vuelvo a insertar.

No es una pregunta crítica, pero me interesaría entender qué está pasando.

Matthew McGiffen
fuente
2
¿En cuántos archivos del grupo de archivos está insertando? Lo intenté un par de veces en 2016 y las dos veces la tabla se dividió en 3584 páginas con 279 filas y 1 con 64. Los dos tamaños de muestra diferentes que vi fueron 314712 y 315270, ambos múltiplos exactos de 279.
Martin Smith
1
@JoeObbish: siempre lee páginas enteras AFAIK, así que no me sorprendió eso. Sin embargo, por alguna razón, pensé que los números en la pregunta no coincidían con ese patrón. Pero habiendo rehecho las matemáticas que hacen. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279Y 314154=1126por lo que la varianza es el número de páginas de muestra.
Martin Smith
@MartinSmithJust the one file - la cifra 279 es interesante, siempre me gusta entender los patrones involucrados
Matthew McGiffen

Respuestas:

26

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 biginttabla base ( ), partition_idp . 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 REPEATABLEcláusula.

    • Para la muestra UPDATE STATISTICS, el REPEATABLEvalor es 1.
    • Este valor se expone en el m_randomSeedelemento 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 REPEATABLEvalor 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 41A7hexadecimal (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 StatManconsulta 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 REPEATABLEvalor. El REPEATABLEvalor 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 TABLESAMPLEque diga la cláusula (incluso cero por ciento).

Paul White dice GoFundMonica
fuente
11

Esta es una excelente pregunta! Comenzaré con lo que sé con certeza y luego pasaré a la especulación. Muchos detalles sobre esto en mi blog aquí .

Las actualizaciones de estadísticas muestreadas se usan TABLESAMPLEdetrás de escena. Es bastante fácil encontrar documentación sobre eso en línea. Sin embargo, creo que no se sabe bien que las filas devueltas TABLESAMPLEdependen parcialmente hobt_iddel objeto. Cuando suelta y hobt_idvuelve a crear el objeto, obtiene un nuevo, por lo que las filas devueltas por muestreo aleatorio son diferentes.

Si elimina y vuelve a insertar los datos, hobt_idpermanece igual. Mientras los datos se presenten de la misma manera en el disco (una exploración de orden de asignación devuelve los mismos resultados en el mismo orden), los datos muestreados no deberían cambiar.

También puede cambiar el número de filas muestreadas reconstruyendo el índice agrupado en la tabla. Por ejemplo:

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

En cuanto a por qué sucede eso, creo que es porque SQL Server escanea el índice agrupado en lugar del índice no agrupado cuando recopila estadísticas muestreadas en un índice. También creo que hay un valor oculto (para aquellos de nosotros que rastreamos las consultas ocultas de actualización de estadísticas) para REPEATABLEusar con TABLESAMPLE. No he probado nada de eso, pero explica por qué su histograma y las filas muestreadas cambian con una reconstrucción del índice agrupado.

Joe Obbish
fuente