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 bigint
tabela base ( ), partition_id
por 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 REPEATABLE
cláusula
- Para amostrado
UPDATE STATISTICS
, o REPEATABLE
valor é 1.
- Esse valor é exposto no
m_randomSeed
elemento 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 REPEATABLE
valor 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 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 incorporado sqlmin!UnOrderPageScanner::SetupSubScanner
.
StatMan
Para a StatMan
consulta 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 REPEATABLE
valor. O REPEATABLE
valor 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 TABLESAMPLE
cláusula (mesmo zero por cento).