Andei investigando limites de amostragem com atualizações de estatísticas no SQL Server (2012) e notei um 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, estou descobrindo que o "Rows Sampled" varia com 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 valor fosse o mesmo toda vez que a tabela fosse idêntica. A propósito, não recebo esse comportamento se apenas excluir os dados e reinserir.
Não é uma pergunta crítica, mas eu estaria interessado em entender o que está acontecendo.
Fundo
Os dados para o objeto de estatísticas são coletados usando uma declaração do formulário:
Você pode coletar essa declaração com Extended Events 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 se qualifica ou não. O gerador usado nesta instância é o gerador de números aleatórios Lehmer com valores de parâmetros conforme mostrado abaixo:
O valor de é calculado como a soma de:
Xseed
A parte inteira baixa da
bigint
tabela base ( ),partition_id
por exemploO valor especificado na
REPEATABLE
cláusulaUPDATE STATISTICS
, oREPEATABLE
valor é 1.m_randomSeed
elemento das informações de depuração interna do método de acesso mostradas nos planos de execução quando o sinalizador de rastreamento 8666 está habilitado, por exemplo<Field FieldName="m_randomSeed" FieldValue="1" />
Para o SQL Server 2012, esse cálculo ocorre em
sqlmin!UnOrderPageScanner::StartScan
:onde memory at
[rcx+30h]
contém os 32 bits mais baixos do id da partição e memory at[rcx+2Ch]
contém oREPEATABLE
valor em uso.O gerador de números aleatórios é inicializado posteriormente no mesmo método, chamando
sqlmin!RandomNumGenerator::Init
, onde a instrução:...multiplica a semente por
41A7
hexadecimal (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 embutido em
sqlmin!UnOrderPageScanner::SetupSubScanner
.StatMan
Para a
StatMan
consulta de exemplo mostrada acima, as mesmas páginas serão coletadas para a instrução T-SQL:Isso corresponderá à saída de:
Caso de borda
Uma consequência do uso do gerador de números aleatórios MINSTD Lehmer é que os valores de semente zero e int.max não devem ser usados, pois isso fará com que o algoritmo produza uma sequência de zeros (selecionando cada página).
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 for int.max (
0x7FFFFFFF
= 2 31 - 1).Podemos projetar esse cenário, pois a semente inicial é calculada como a soma dos 32 bits mais baixos do ID da partição e do
REPEATABLE
valor. OREPEATABLE
valor que resultará na semente sendo int.max e, portanto, em cada página selecionada para amostra é:Trabalhando isso em um exemplo completo:
Isso selecionará todas as linhas em todas as páginas, independentemente da
TABLESAMPLE
cláusula (mesmo zero por cento).Essa é uma excelente pergunta! Vou começar com o que sei com certeza e depois passar para a especulação. Muitos detalhes sobre isso no meu post aqui .
Atualizações de estatísticas de amostra usadas
TABLESAMPLE
nos bastidores. É muito fácil encontrar documentação sobre isso online. No entanto, acredito que não seja bem conhecido que as linhas retornadas porTABLESAMPLE
dependem parcialmentehobt_id
do objeto. Quando você descarta e recria o objeto, obtém um novohobt_id
para que as linhas retornadas por amostragem aleatória sejam diferentes.Se você excluir e reinserir os dados, eles
hobt_id
permanecem os mesmos. Contanto 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 clusterizado na tabela. Por exemplo:
Por que isso acontece, acredito que seja porque o SQL Server verifica o índice clusterizado em vez do índice não clusterizado ao coletar estatísticas de amostra em um índice. Eu também acho que há um valor oculto (para aqueles de nós que rastreiam as consultas de atualização de estatísticas ocultas) para
REPEATABLE
usado comTABLESAMPLE
. Eu não provei nada disso, mas explica por que seu histograma e as linhas amostradas mudam com uma reconstrução do índice clusterizado.Eu vi isso em Inside Microsoft SQL Server 2008: T-SQL Querying de Itzik Ben-Gan e não posso adicioná-lo como comentário, então posto aqui, acho que é interessante para outros também:
Veja também Amostragem Usando TABLESAMPLE de Roji. P. Thomas.