Vou limitar esta postagem a discutir estatísticas de coluna única, porque ela já será bastante longa e você estará interessado em saber como o SQL Server agrupa os dados em etapas de histograma. Para estatísticas de várias colunas, o histograma é criado apenas na coluna principal.
Quando o SQL Server determina que é necessária uma atualização de estatísticas, ela inicia uma consulta oculta que lê todos os dados de uma tabela ou uma amostra dos dados da tabela. Você pode visualizar essas consultas com eventos estendidos. Há uma função chamada StatMan
no SQL Server envolvida na criação dos histogramas. Para objetos estatísticos simples, existem pelo menos dois tipos diferentes de StatMan
consultas (existem consultas diferentes para atualizações rápidas de estatísticas e eu suspeito que o recurso de estatísticas incrementais nas tabelas particionadas também use uma consulta diferente).
O primeiro apenas captura todos os dados da tabela sem filtragem. Você pode ver isso quando a tabela é muito pequena ou reunir estatísticas com a FULLSCAN
opção:
CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);
-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
SELECT TOP 100 PERCENT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
ORDER BY [SC0]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 16);
O SQL Server escolhe o tamanho de amostra automático com base no tamanho da tabela (acho que é o número de linhas e páginas da tabela). Se uma tabela for muito grande, o tamanho automático da amostra ficará abaixo de 100%. Aqui está o que eu recebi para a mesma tabela com 1 milhão de linhas:
-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM
(
SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000]
FROM
(
SELECT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY [SC0], [SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);
TABLESAMPLE
está documentado, mas StatMan e step_direction não. aqui, o SQL Server mostra cerca de 66,6% dos dados da tabela para criar o histograma. O que isso significa é que você pode obter um número diferente de etapas do histograma ao atualizar estatísticas (sem FULLSCAN
) nos mesmos dados. Nunca observei isso na prática, mas não vejo por que não seria possível.
Vamos executar alguns testes em dados simples para ver como as estatísticas mudam ao longo do tempo. Abaixo está um código de teste que escrevi para inserir números inteiros seqüenciais em uma tabela, reunir estatísticas após cada inserção e salvar informações sobre as estatísticas em uma tabela de resultados. Vamos começar inserindo apenas uma linha por vez, até 10000. Cama de teste:
DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE X_STATS_RESULTS;
SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
SELECT @stats_id = stats_id FROM sys.stats
WHERE OBJECT_ID = @table_object_id
AND name = 'X_STATS_SEQ_INT_FULL';
SET @loop_num = 0;
WHILE @loop_num < @num_of_loops
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed
INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps
FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
END;
END;
Para esses dados, o número de etapas do histograma aumenta rapidamente para 200 (atinge primeiro o número máximo de etapas com 397 linhas), permanece em 199 ou 200 até 1485 linhas estarem na tabela e depois diminui lentamente até que o histograma tenha apenas 3 ou 4 passos. Aqui está um gráfico de todos os dados:
Aqui está o histograma para 10 mil linhas:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 1 0 1
9999 9997 1 9997 1
10000 0 1 0 1
É um problema que o histograma tenha apenas 3 etapas? Parece que as informações são preservadas do nosso ponto de vista. Observe que, como o tipo de dados é um INTEGER, podemos descobrir quantas linhas existem na tabela para cada número inteiro de 1 a 10000. Normalmente, o SQL Server também pode descobrir isso, embora haja alguns casos em que isso não dá certo. . Veja esta postagem do SE para obter um exemplo disso.
O que você acha que acontecerá se excluirmos uma única linha da tabela e atualizarmos as estatísticas? Idealmente, teríamos outra etapa do histograma para mostrar que o número inteiro ausente não está mais na tabela.
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
Isso é um pouco decepcionante. Se estivéssemos construindo um histograma manualmente, adicionaríamos uma etapa para cada valor ausente. O SQL Server está usando um algoritmo de uso geral; portanto, para alguns conjuntos de dados, podemos criar um histograma mais adequado do que o código que ele usa. Obviamente, a diferença prática entre obter 0 ou 1 linha de uma tabela é muito pequena. Eu obtenho os mesmos resultados ao testar com 20000 linhas em que cada número inteiro tem 2 linhas na tabela. O histograma não ganha etapas quando eu excluo os dados.
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 2 0 1
9999 19994 2 9997 2
10000 0 2 0 1
Se eu testar com 1 milhão de linhas com cada número inteiro com 100 linhas na tabela, obtenho resultados um pouco melhores, mas ainda assim posso construir um histograma melhor manualmente.
truncate table X_SEQ_NUM;
BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps
Histograma final:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 100 0 1
998 99600 100 996 100
3983 298100 100 2981 100
9999 600900 100 6009 100
10000 0 100 0 1
Vamos testar ainda mais com números inteiros sequenciais, mas com mais linhas na tabela. Observe que, para tabelas muito pequenas, a especificação manual de um tamanho de amostra não terá efeito; portanto, adicionarei 100 linhas em cada inserção e reunirei estatísticas a cada vez até 1 milhão de linhas. Vejo um padrão semelhante ao de antes, exceto quando chego a 637300 linhas na tabela, não amostro mais 100% das linhas na tabela com a taxa de amostragem padrão. À medida que ganho linhas, o número de etapas do histograma aumenta. Talvez isso ocorra porque o SQL Server acaba com mais lacunas nos dados à medida que o número de linhas sem amostra na tabela aumenta. Eu não atendo 200 etapas, mesmo em linhas de 1 milhão de linhas, mas se eu continuasse adicionando linhas, espero chegar lá e, eventualmente, começar a voltar para baixo.
O eixo X é o número de linhas na tabela. À medida que o número de linhas aumenta, as linhas amostradas variam um pouco e não ultrapassam 650k.
Agora vamos fazer alguns testes simples com dados VARCHAR.
CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);
Aqui estou inserindo 200 números (como seqüências de caracteres) junto com NULL.
INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps
Observe que NULL sempre obtém sua própria etapa de histograma quando encontrada na tabela. O SQL Server poderia ter me dado exatamente 201 etapas para preservar todas as informações, mas não o fez. Tecnicamente, as informações são perdidas porque '1111' classifica entre '1' e '2', por exemplo.
Agora vamos tentar inserir caracteres diferentes em vez de apenas números inteiros:
truncate table X_SEQ_STR;
INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2
Nenhuma diferença real do último teste.
Agora vamos tentar inserir caracteres, mas colocar números diferentes de cada caractere na tabela. Por exemplo, CHAR(11)
tem 1 linha, CHAR(12)
2 linhas, etc.
truncate table X_SEQ_STR;
DECLARE
@loop_num INT;
BEGIN
SET NOCOUNT ON;
SET @loop_num = 0;
WHILE @loop_num < 200
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_STR WITH (TABLOCK)
SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
END;
END;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0
Como antes, ainda não recebo exatamente 200 etapas de histograma. No entanto, muitas das etapas têm RANGE_ROWS
0.
Para o teste final, vou inserir uma sequência aleatória de 5 caracteres em cada loop e coletar estatísticas a cada vez. Aqui está o código da string aleatória:
char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))
Aqui está o gráfico de linhas na tabela vs etapas do histograma:
Observe que o número de etapas não cai abaixo de 100 quando começa a subir e descer. Ouvi de algum lugar (mas não posso obtê-lo no momento) que o algoritmo de criação de histograma do SQL Server combina etapas do histograma à medida que o espaço para elas fica sem espaço. Assim, você pode acabar com mudanças drásticas no número de etapas apenas adicionando um pouco de dados. Aqui está uma amostra dos dados que eu achei interessantes:
ROWS_IN_TABLE ROWS_SAMPLED STEPS
36661 36661 133
36662 36662 143
36663 36663 143
36664 36664 141
36665 36665 138
Mesmo ao amostrar FULLSCAN
, adicionar uma única linha pode aumentar o número de etapas em 10, mantê-lo constante, depois diminuí-lo em 2 e depois em 3.
O que podemos resumir disso tudo? Não posso provar nada disso, mas essas observações parecem verdadeiras:
- O SQL Server usa um algoritmo de uso geral para criar os histogramas. Para algumas distribuições de dados, pode ser possível criar uma representação mais completa dos dados manualmente.
- Se houver dados NULL na tabela e a consulta de estatísticas o encontrar, esses dados sempre obterão sua própria etapa do histograma.
- O valor mínimo encontrado na tabela obtém sua própria etapa do histograma com
RANGE_ROWS
= 0.
- O valor máximo encontrado na tabela será o final
RANGE_HI_KEY
da tabela.
- À medida que o SQL Server coleta mais dados, pode ser necessário combinar as etapas existentes para liberar espaço para os novos dados encontrados. Se você observar histogramas suficientes, poderá ver valores comuns repetidos para
DISTINCT_RANGE_ROWS
ou RANGE_ROWS
. Por exemplo, 255 aparece várias vezes para RANGE_ROWS
e DISTINCT_RANGE_ROWS
para o caso de teste final aqui.
- Para distribuições de dados simples, você pode ver o SQL Server combinar dados seqüenciais em uma etapa do histograma que não causa perda de informações. No entanto, ao adicionar lacunas aos dados, o histograma pode não se ajustar da maneira que você esperaria.
Quando tudo isso é um problema? É um problema quando uma consulta apresenta um desempenho ruim devido a um histograma que é incapaz de representar a distribuição de dados de uma maneira que o otimizador de consultas tome boas decisões. Eu acho que há uma tendência de pensar que ter mais etapas de histograma é sempre melhor e que haja consternação quando o SQL Server gerar um histograma em milhões de linhas ou mais, mas não usa exatamente 200 ou 201 etapas de histograma. No entanto, tenho visto muitos problemas de estatísticas, mesmo quando o histograma tem 200 ou 201 etapas. Não temos controle sobre quantas etapas do histograma que o SQL Server gera para um objeto de estatística, para que eu não me preocupasse. No entanto, existem algumas etapas que você pode executar quando tiver consultas com desempenho ruim causadas por problemas de estatísticas. Vou dar uma visão geral extremamente breve.
A coleta completa de estatísticas pode ajudar em alguns casos. Para tabelas muito grandes, o tamanho da amostra automática pode ser menor que 1% das linhas da tabela. Às vezes, isso pode levar a planos ruins, dependendo da interrupção dos dados na coluna. A documentação da Microsofts para CREATE STATISTICS e UPDATE STATISTICS diz o mesmo:
AMOSTRA é útil para casos especiais em que o plano de consulta, com base na amostragem padrão, não é ideal. Na maioria das situações, não é necessário especificar SAMPLE porque o otimizador de consulta já usa amostragem e determina o tamanho da amostra estatisticamente significativo por padrão, conforme necessário para criar planos de consulta de alta qualidade.
Para a maioria das cargas de trabalho, uma verificação completa não é necessária e a amostragem padrão é adequada. No entanto, certas cargas de trabalho sensíveis a distribuições de dados muito variadas podem exigir um tamanho maior da amostra ou até uma verificação completa.
Em alguns casos, a criação de estatísticas filtradas pode ajudar. Você pode ter uma coluna com dados inclinados e muitos valores distintos. Se houver determinados valores nos dados geralmente filtrados, você poderá criar um histograma estatístico apenas para esses valores comuns. O otimizador de consulta pode usar as estatísticas definidas em um intervalo menor de dados em vez das estatísticas definidas em todos os valores da coluna. Você ainda não tem garantia de obter 200 etapas no histograma, mas se você criar as estatísticas filtradas em apenas um valor, será feito um histograma nesse valor.
Usar uma exibição particionada é uma maneira de obter efetivamente mais de 200 etapas para uma tabela. Suponha que você possa facilmente dividir uma tabela grande em uma tabela por ano. Você cria uma UNION ALL
exibição que combina todas as tabelas anuais. Cada tabela terá seu próprio histograma. Observe que as novas estatísticas incrementais introduzidas no SQL Server 2014 permitem apenas que as atualizações de estatísticas sejam mais eficientes. O otimizador de consulta não usará as estatísticas criadas por partição.
Existem muitos outros testes que podem ser executados aqui, por isso encorajo você a experimentar. Eu fiz esse teste no SQL Server 2014 express, então realmente não há nada para você.