Como é decidido o número de etapas do histograma em Estatísticas

11

Como é decidido o número de etapas do histograma em Estatística no SQL Server?

Por que está restrito a 200 etapas, mesmo que minha coluna chave tenha mais de 200 valores distintos? Existe algum fator decisivo?


Demo

Definição de esquema

CREATE TABLE histogram_step
  (
     id   INT IDENTITY(1, 1),
     name VARCHAR(50),
     CONSTRAINT pk_histogram_step PRIMARY KEY (id)
  )

Inserindo 100 registros na minha tabela

INSERT INTO histogram_step
            (name)
SELECT TOP 100 name
FROM   sys.syscolumns

Atualizando e Verificando as Estatísticas

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)

Etapas do histograma:

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|            3 |          1 |       1 |                   1 |              1 |
|            5 |          1 |       1 |                   1 |              1 |
|            7 |          1 |       1 |                   1 |              1 |
|            9 |          1 |       1 |                   1 |              1 |
|           11 |          1 |       1 |                   1 |              1 |
|           13 |          1 |       1 |                   1 |              1 |
|           15 |          1 |       1 |                   1 |              1 |
|           17 |          1 |       1 |                   1 |              1 |
|           19 |          1 |       1 |                   1 |              1 |
|           21 |          1 |       1 |                   1 |              1 |
|           23 |          1 |       1 |                   1 |              1 |
|           25 |          1 |       1 |                   1 |              1 |
|           27 |          1 |       1 |                   1 |              1 |
|           29 |          1 |       1 |                   1 |              1 |
|           31 |          1 |       1 |                   1 |              1 |
|           33 |          1 |       1 |                   1 |              1 |
|           35 |          1 |       1 |                   1 |              1 |
|           37 |          1 |       1 |                   1 |              1 |
|           39 |          1 |       1 |                   1 |              1 |
|           41 |          1 |       1 |                   1 |              1 |
|           43 |          1 |       1 |                   1 |              1 |
|           45 |          1 |       1 |                   1 |              1 |
|           47 |          1 |       1 |                   1 |              1 |
|           49 |          1 |       1 |                   1 |              1 |
|           51 |          1 |       1 |                   1 |              1 |
|           53 |          1 |       1 |                   1 |              1 |
|           55 |          1 |       1 |                   1 |              1 |
|           57 |          1 |       1 |                   1 |              1 |
|           59 |          1 |       1 |                   1 |              1 |
|           61 |          1 |       1 |                   1 |              1 |
|           63 |          1 |       1 |                   1 |              1 |
|           65 |          1 |       1 |                   1 |              1 |
|           67 |          1 |       1 |                   1 |              1 |
|           69 |          1 |       1 |                   1 |              1 |
|           71 |          1 |       1 |                   1 |              1 |
|           73 |          1 |       1 |                   1 |              1 |
|           75 |          1 |       1 |                   1 |              1 |
|           77 |          1 |       1 |                   1 |              1 |
|           79 |          1 |       1 |                   1 |              1 |
|           81 |          1 |       1 |                   1 |              1 |
|           83 |          1 |       1 |                   1 |              1 |
|           85 |          1 |       1 |                   1 |              1 |
|           87 |          1 |       1 |                   1 |              1 |
|           89 |          1 |       1 |                   1 |              1 |
|           91 |          1 |       1 |                   1 |              1 |
|           93 |          1 |       1 |                   1 |              1 |
|           95 |          1 |       1 |                   1 |              1 |
|           97 |          1 |       1 |                   1 |              1 |
|           99 |          1 |       1 |                   1 |              1 |
|          100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

Como podemos ver, existem 53 etapas no histograma.

Mais uma vez inserindo alguns milhares de registros

INSERT INTO histogram_step
            (name)
SELECT TOP 10000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b

Atualizando e Verificando as Estatísticas

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step)

Agora, as etapas do histograma são reduzidas para 4 etapas

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|        10088 |      10086 |       1 |               10086 |              1 |
|        10099 |         10 |       1 |                  10 |              1 |
|        10100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

Mais uma vez inserindo alguns milhares de registros

INSERT INTO histogram_step
            (name)
SELECT TOP 100000 b.name
FROM   sys.syscolumns a
       CROSS JOIN sys.syscolumns b

Atualizando e Verificando as Estatísticas

UPDATE STATISTICS histogram_step WITH fullscan

DBCC show_statistics('histogram_step', pk_histogram_step) 

Agora, as etapas do histograma são reduzidas para 3 etapas

+--------------+------------+---------+---------------------+----------------+
| RANGE_HI_KEY | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE_ROWS |
+--------------+------------+---------+---------------------+----------------+
|            1 |          0 |       1 |                   0 |              1 |
|       110099 |     110097 |       1 |              110097 |              1 |
|       110100 |          0 |       1 |                   0 |              1 |
+--------------+------------+---------+---------------------+----------------+

Alguém pode me dizer como essas etapas são decididas?

P ரதீப்
fonte
3
200 foi uma escolha arbitrária. Não tem nada a ver com quantos valores distintos você possui em uma tabela específica. Se você quer saber por que 200 foi escolhido, você terá que perguntar a um engenheiro da equipe de 1990 SQL Server, não seus pares
Aaron Bertrand
11
@AaronBertrand - Obrigado .. Então, como são decididos esses números de etapas
#
11
Não há decisão. O limite superior é 200. Ponto final. Bem, tecnicamente, é 201, mas é uma história para outro dia.
Aaron Bertrand
11
Eu tenho uma pergunta semelhante sobre as estimativas intrastep, pode ser útil dba.stackexchange.com/questions/148523/...
jesijesi

Respostas:

14

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 StatManno SQL Server envolvida na criação dos histogramas. Para objetos estatísticos simples, existem pelo menos dois tipos diferentes de StatManconsultas (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 FULLSCANopçã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);

TABLESAMPLEestá 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:

primeiro gráfico

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.

gráfico 2

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

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: gráfico 3

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_KEYda 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_ROWSou RANGE_ROWS. Por exemplo, 255 aparece várias vezes para RANGE_ROWSe DISTINCT_RANGE_ROWSpara 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 ALLexibiçã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ê.

Joe Obbish
fonte
4
Consulte google.com/patents/US6714938
Paul White 9