Eu já vi esse mesmo problema de densidade em alguns dos índices não clusterizados nos maiores bancos de dados aos quais tenho acesso. Primeiro, começarei com algumas observações que fiz sobre histogramas e cálculos de densidade:
- O SQL Server pode usar a chave primária da tabela para inferir algo sobre a densidade de ambas as colunas. Isso significa que a densidade que inclui as colunas PK geralmente será muito precisa.
- O cálculo da densidade para a primeira coluna nas estatísticas é consistente com o histograma. Se o histograma não modelar bem os dados, a densidade poderá estar desativada.
- Para criar o histograma, a
StatMan
função faz inferências sobre os dados que estão faltando. O comportamento pode mudar dependendo do tipo de dados da coluna.
Para uma maneira de analisar o problema, suponha que você faça uma amostra de 100 linhas de uma tabela de 10000 linhas e obtenha 100 valores distintos. Um palpite para o que os outros dados da tabela são: existem 10000 valores exclusivos. Outro palpite é que existem 100 valores distintos, mas cada um dos valores é repetido 100 vezes. O segundo palpite pode parecer irracional para você, com o qual vou concordar. No entanto, como você equilibra as duas abordagens quando os dados amostrados retornam desigualmente distribuídos? Existe um conjunto de algoritmos desenvolvidos para isso pela Microsoft contidos na StatMan
função. Os algoritmos podem não funcionar para todas as interrupções de dados e todos os níveis de amostra.
Vamos passar por um exemplo relativamente simples. Vou usar VARCHAR
colunas como na sua tabela para ver o mesmo comportamento. No entanto, apenas adicionarei um valor inclinado à tabela. Estou testando no SQL Server 2016 SP1. Comece com 100 mil linhas com 100 mil valores exclusivos para a FK
coluna:
DROP TABLE IF EXISTS X_STATS_SMALL;
CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);
CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
Aqui estão alguns exemplos das estatísticas:
╔═════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠═════════════╬════════════════╬═════════╣
║ 1.00001E-05 ║ 4.888205 ║ FK ║
║ 1.00001E-05 ║ 9.77641 ║ FK, ID ║
╚═════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 1005 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10648 ║ 665.0898 ║ 1 ║ 664 ║ 1.002173 ║
║ 10968 ║ 431.6008 ║ 1 ║ 432 ║ 1 ║
║ 11182 ║ 290.0924 ║ 1 ║ 290 ║ 1 ║
║ 1207 ║ 445.7517 ║ 1 ║ 446 ║ 1 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99989 ║ 318.3941 ║ 1 ║ 318 ║ 1 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
Para dados distribuídos uniformemente com um valor único por linha, obtemos uma densidade precisa, mesmo com uma VARCHAR
coluna de histograma e um tamanho de amostra de 14294 linhas.
Agora vamos adicionar um valor distorcido e atualizar as estatísticas novamente:
-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000', REPLICATE('Z', 900)
FROM dbo.GetNums(70000);
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
Com um tamanho de amostra de 17010 linhas, a densidade da primeira coluna é menor do que deveria ser:
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 6.811061E-05 ║ 4.935802 ║ FK ║
║ 5.882353E-06 ║ 10.28007 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
║ 10039 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10978 ║ 956.9945 ║ 1 ║ 138 ║ 6.954391 ║
║ 11472 ║ 621.0283 ║ 1 ║ 89 ║ 6.941863 ║
║ 1179 ║ 315.6046 ║ 1 ║ 46 ║ 6.907561 ║
║ 11909 ║ 91.62713 ║ 1 ║ 14 ║ 6.74198 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 35000 ║ 376.6893 ║ 69195.05 ║ 54 ║ 6.918834 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99966 ║ 325.7854 ║ 1 ║ 47 ║ 6.909731 ║
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝
É surpreendente que AVG_RANGE_ROWS
seja bastante uniforme para todas as etapas em torno de 6,9, mesmo para baldes de chaves para os quais a amostra não pôde encontrar valores duplicados. Não sei por que isso é. A explicação mais provável é que o algoritmo usado para adivinhar as páginas ausentes não se dá bem com essa distribuição de dados e tamanho da amostra.
Como afirmado anteriormente, é possível calcular a densidade da coluna FK usando o histograma. A soma dos DISTINCT_RANGE_ROWS
valores de todas as etapas é 14497. Existem 179 etapas do histograma, portanto a densidade deve ser de cerca de 1 / (179 + 14497) = 0,00006813845, o que é bastante próximo do valor relatado.
Testar com uma tabela maior pode mostrar como o problema pode piorar à medida que a tabela aumenta. Desta vez, começaremos com 1 milhão de linhas:
DROP TABLE IF EXISTS X_STATS_LARGE;
CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);
CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
O objeto de estatísticas ainda não é interessante. A densidade de FK
é 1.025289E-06, que é quase exata (1.0E-06).
Agora vamos adicionar um valor distorcido e atualizar as estatísticas novamente:
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000', REPLICATE('Z', 900)
FROM dbo.Getnums(700000);
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
Com um tamanho de amostra de 45627 linhas, a densidade da primeira coluna é pior do que antes:
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 2.60051E-05 ║ 5.93563 ║ FK ║
║ 5.932542E-07 ║ 12.28485 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 100023 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 107142 ║ 8008.354 ║ 1 ║ 306 ║ 26.17787 ║
║ 110529 ║ 4361.357 ║ 1 ║ 168 ║ 26.02392 ║
║ 114558 ║ 3722.193 ║ 1 ║ 143 ║ 26.01217 ║
║ 116696 ║ 2556.658 ║ 1 ║ 98 ║ 25.97568 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 350000 ║ 5000.522 ║ 700435 ║ 192 ║ 26.03268 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 999956 ║ 2406.266 ║ 1 ║ 93 ║ 25.96841 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
AVG_RANGE_ROWS
é até 26. Curiosamente, se eu alterar o tamanho da amostra para 170100 linhas (10X na outra tabela), o valor médio de AVG_RANGE_ROWS
será novamente 6,9. À medida que sua tabela aumenta, o SQL Server seleciona um tamanho de amostra menor, o que significa que ele precisa fazer palpites sobre uma porcentagem maior de páginas na tabela. Isso pode exagerar os problemas estatísticos para certos tipos de distorção de dados.
Em conclusão, é importante lembrar que o SQL Server não calcula a densidade assim:
SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);
O que para algumas distribuições de dados será muito preciso. Em vez disso, usa algoritmos não documentados . Na sua pergunta, você disse que seus dados não estavam distorcidos, mas o INSTANCEELEMENTID
valor com o maior número de IDs associados tem 12 e o número mais comum é 1. Para os fins dos algoritmos usados por eles, Statman
esses dados podem ser distorcidos.
Nesse ponto, não há nada que você possa fazer, exceto reunir estatísticas com uma taxa de amostragem mais alta. Uma estratégia comum é reunir estatísticas com FULLSCAN
e NORECOMPUTE
. Você pode atualizar as estatísticas com um trabalho em qualquer intervalo que faça sentido para a sua taxa de alteração de dados. Na minha experiência, uma FULLSCAN
atualização não é tão ruim quanto a maioria das pessoas pensa, especialmente em relação a um índice. O SQL Server pode apenas verificar o índice inteiro em vez da tabela inteira (como faria em uma tabela de armazenamento de linhas em uma coluna não indexada). Além disso, no SQL Serer 2014, apenas as FULLSCAN
atualizações de estatísticas são feitas em paralelo, para que uma FULLSCAN
atualização possa terminar mais rapidamente do que algumas atualizações de amostra.
tablesample