Comportamento estranho com tamanhos de amostra para atualizações de estatísticas

25

Venho investigando os limites de amostragem com atualizações de estatísticas no SQL Server (2012) e notei algum comportamento curioso. Basicamente, o número de linhas amostradas parece variar em algumas circunstâncias - mesmo com o mesmo conjunto de dados.

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

Quando olho para a saída do SHOW_STATISTICS, percebo que as "Linhas amostradas" variam a cada execução completa (ou seja, a tabela é descartada, recriada e repovoada).

Por exemplo:

Linhas amostradas

  • 318618
  • 319240
  • 324198
  • 314154

Minha expectativa era que esse número fosse o mesmo sempre que a tabela for idêntica. A propósito, eu não entendo esse comportamento se apenas excluir os dados e inseri-los novamente.

Não é uma pergunta crítica, mas eu estaria interessado em entender o que está acontecendo.

Matthew McGiffen
fonte
2
Em quantos arquivos no grupo de arquivos você está inserindo? Eu tentei algumas vezes em 2016 e as duas vezes a tabela foi dividida em 3584 páginas com 279 linhas e 1 com 64. Os dois tamanhos de amostra diferentes que vi foram 314712 e 315270 - ambos os múltiplos exatos de 279.
Martin Smith
11
@ JoeObbish - Sempre lê páginas inteiras AFAIK, então não fiquei surpreso com isso. Por alguma razão, pensei que os números da pergunta não correspondessem a esse padrão. Mas tendo refeito a matemática que eles fazem. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279E 314154=1126por isso a variância é o número de páginas amostrados.
Martin Smith
@MartinSmithApenas um arquivo - a figura 279 é interessante, eu sempre gosto de entender os padrões envolvidos
Matthew McGiffen

Respostas:

26

fundo

Os dados para o objeto de estatísticas são coletados usando uma declaração do formulário:

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)

Você pode coletar esta declaração com Eventos Estendidos ou Profiler ( SP:StmtCompleted).

As consultas de geração de estatísticas geralmente acessam a tabela base (em vez de um índice não clusterizado) para evitar o agrupamento de valores que ocorre naturalmente em páginas de índice não clusterizadas.

O número de linhas amostradas depende do número de páginas inteiras selecionadas para amostragem. Cada página da tabela está selecionada ou não. Todas as linhas nas páginas selecionadas contribuem para as estatísticas.

Números aleatórios

O SQL Server usa um gerador de números aleatórios para decidir se uma página é qualificada ou não. O gerador usado nesta instância é o gerador de números aleatórios Lehmer com valores de parâmetros, como mostrado abaixo:

X seguinte = X semente * 7 5 mod (2 31-1 )

O valor de é calculado como a soma de:Xseed

  • A parte inteira baixa da biginttabela base ( ), partition_idpor exemplo,

    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;
  • O valor especificado na REPEATABLEcláusula

    • Para amostrado UPDATE STATISTICS, o REPEATABLEvalor é 1.
    • Esse valor é exposto no m_randomSeedelemento das informações de depuração internas do método de acesso mostradas nos planos de execução quando o sinalizador de rastreamento 8666 está ativado, por exemplo<Field FieldName="m_randomSeed" FieldValue="1" />

Para o SQL Server 2012, esse cálculo ocorre em sqlmin!UnOrderPageScanner::StartScan:

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

onde a memória em [rcx+30h]contém os 32 bits baixos do ID da partição e a memória em [rcx+2Ch]contém o REPEATABLEvalor em uso.

O gerador de números aleatórios é inicializado posteriormente no mesmo método, chamando sqlmin!RandomNumGenerator::Init, onde a instrução:

imul    r9d,r9d,41A7h

... multiplica a semente por 41A7hexadecimal (16807 decimal = 7 5 ), como mostrado na equação acima.

Números aleatórios posteriores (para páginas individuais) são gerados usando o mesmo código básico incorporado sqlmin!UnOrderPageScanner::SetupSubScanner.

StatMan

Para a StatManconsulta de exemplo mostrada acima, as mesmas páginas serão coletadas como para a instrução 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

Isso corresponderá à saída 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 borda

Uma conseqüência do uso do gerador de números aleatórios MINSTD Lehmer é que os valores de zero e int.max não devem ser usados, pois isso resultará no algoritmo produzindo uma sequência de zeros (selecionando todas as páginas).

O código detecta zero e usa um valor do 'relógio' do sistema como a semente nesse caso. Não faz o mesmo se a semente estiver int.max ( 0x7FFFFFFF= 2 31 - 1).

Podemos projetar esse cenário, pois a semente inicial é calculada como a soma dos 32 bits baixos do ID da partição e do REPEATABLEvalor. O REPEATABLEvalor que resultará na semente sendo int.max e, portanto, todas as páginas selecionadas para amostra são:

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;

Trabalhando isso em um exemplo 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);

Isso selecionará todas as linhas de todas as páginas, independentemente da TABLESAMPLEcláusula (mesmo zero por cento).

Paul White diz que a GoFundMonica
fonte
11

Esta é uma excelente pergunta! Vou começar com o que tenho certeza e depois passar para a especulação. Muitos detalhes sobre isso no meu post aqui .

As atualizações de estatísticas amostradas são usadas TABLESAMPLEnos bastidores. É muito fácil encontrar documentação sobre isso online. No entanto, acredito que não é bem sabido que as linhas retornadas por TABLESAMPLEdependem parcialmente hobt_idda do objeto. Quando você solta e recria o objeto, obtém um novo hobt_idpara que as linhas retornadas por amostragem aleatória sejam diferentes.

Se você excluir e reinserir os dados, hobt_idos mesmos permanecerão. Desde que os dados sejam dispostos da mesma maneira no disco (uma varredura de ordem de alocação retorna os mesmos resultados na mesma ordem), os dados amostrados não devem ser alterados.

Você também pode alterar o número de linhas amostradas recriando o índice em cluster na tabela. Por exemplo:

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

Por que isso acontece, acredito que é porque o SQL Server verifica o índice em cluster em vez do índice não clusterizado ao reunir estatísticas de amostra em um índice. Também acho que há um valor oculto (para aqueles que rastreiam as consultas de atualização de estatísticas ocultas) para serem REPEATABLEusados TABLESAMPLE. Não provei nada disso, mas explica por que o histograma e as linhas amostradas são alteradas com uma reconstrução do índice clusterizado.

Joe Obbish
fonte