He visto este mismo problema de densidad en algunos de los índices no agrupados en las bases de datos más grandes a las que tengo acceso. Primero comenzaré con algunas observaciones que he hecho sobre histogramas y cálculos de densidad:
- SQL Server puede usar la clave primaria en la tabla para inferir algo sobre la densidad de ambas columnas. Esto significa que la densidad que incluye las columnas PK generalmente será muy precisa.
- El cálculo de densidad para la primera columna en las estadísticas es consistente con el histograma. Si el histograma no modela bien los datos, entonces la densidad puede estar desactivada.
- Para crear el histograma, la
StatMan
función hace inferencias sobre los datos que faltan. El comportamiento puede cambiar según el tipo de datos de la columna.
Para una forma de ver el problema, suponga que muestrea 100 filas de una tabla de 10000 filas y obtiene 100 valores distintos. Una conjetura sobre el resto de los datos en la tabla es que hay 10000 valores únicos. Otra suposición es que hay 100 valores distintos, pero cada uno de los valores se repite 100 veces. La segunda suposición puede parecerle irracional, con lo que estaré de acuerdo. Sin embargo, ¿cómo equilibra los dos enfoques cuando los datos muestreados vuelven a estar distribuidos de manera desigual? Hay un conjunto de algoritmos desarrollados para esto por Microsoft contenidos en la StatMan
función. Los algoritmos pueden no funcionar para todas las interrupciones de datos y todos los niveles de muestra.
Veamos un ejemplo relativamente simple. Voy a usar VARCHAR
columnas como en su tabla para ver algunos de los mismos comportamientos. Sin embargo, solo agregaré un valor sesgado a la tabla. Estoy probando contra SQL Server 2016 SP1. Comience con 100k filas con 100k valores únicos para la FK
columna:
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;
Aquí hay algunas muestras de las estadí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 datos distribuidos uniformemente con un valor único por fila, obtenemos una densidad precisa, incluso con una VARCHAR
columna de histograma y un tamaño de muestra de 14294 filas.
Ahora agreguemos un valor sesgado y volvamos a actualizar las estadísticas:
-- 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;
Con un tamaño de muestra de 17010 filas, la densidad de la primera columna es menor de lo que debería 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 ║
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝
Es sorprendente que AVG_RANGE_ROWS
sea bastante uniforme para todos los pasos en torno a 6.9, incluso para cubos de claves para los que la muestra no pudo encontrar valores duplicados. No sé por qué es esto. La explicación más probable es que el algoritmo utilizado para adivinar las páginas que faltan no funciona bien con esta distribución de datos y tamaño de muestra.
Como se indicó anteriormente, es posible calcular la densidad de la columna FK utilizando el histograma. La suma de los DISTINCT_RANGE_ROWS
valores para todos los pasos es 14497. Hay 179 pasos de histograma, por lo que la densidad debe ser aproximadamente 1 / (179 + 14497) = 0.00006813845, que está bastante cerca del valor informado.
Las pruebas con una tabla más grande pueden mostrar cómo el problema puede empeorar a medida que la tabla se agranda. Esta vez comenzaremos con filas de 1 M:
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;
El objeto de estadísticas aún no es interesante. La densidad FK
es 1.025289E-06, que es casi exacta (1.0E-06).
Ahora agreguemos un valor sesgado y volvamos a actualizar las estadísticas:
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;
Con un tamaño de muestra de 45627 filas, la densidad de la primera columna es peor 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
es de hasta 26. Curiosamente, si cambio el tamaño de la muestra a 170100 filas (10 veces la otra tabla), el valor promedio de AVG_RANGE_ROWS
nuevamente es de alrededor de 6,9. A medida que su tabla se hace más grande, SQL Server elegirá un tamaño de muestra más pequeño, lo que significa que necesita hacer conjeturas sobre un mayor porcentaje de páginas en la tabla. Esto puede exagerar los problemas de estadísticas para ciertos tipos de sesgo de datos.
En conclusión, es importante recordar que SQL Server no calcula la densidad de esta manera:
SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);
Lo cual para algunas distribuciones de datos será muy preciso. En su lugar, utiliza algoritmos no documentados . En su pregunta, dijo que sus datos no estaban sesgados, pero el INSTANCEELEMENTID
valor con el mayor número de ID asociados tiene 12 y el número más común es 1. A los efectos de los algoritmos utilizados, Statman
eso podría estar sesgado.
En ese momento, no hay nada que pueda hacer al respecto, excepto recopilar estadísticas con una frecuencia de muestreo más alta. Una estrategia común es recopilar estadísticas con FULLSCAN
y NORECOMPUTE
. Puede actualizar las estadísticas con un trabajo en cualquier intervalo que tenga sentido para su tasa de cambio de datos. En mi experiencia, una FULLSCAN
actualización no es tan mala como la mayoría de la gente piensa, especialmente en comparación con un índice. SQL Server puede escanear todo el índice en lugar de toda la tabla (como lo haría para una tabla de almacén de filas en una columna no indexada). Además, en SQL Serer 2014 solo las FULLSCAN
actualizaciones de estadísticas se realizan en paralelo, por lo que una FULLSCAN
actualización puede finalizar más rápido que algunas actualizaciones muestreadas.
tablesample