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).