Resultados extraños de densidad en estadísticas muestreadas

8

Un índice NC obtiene una distribución estadística totalmente diferente cuando se estima con muestreo vs exploración completa; la muestra que tiene un extraño vector de densidad. Esto da como resultado planes de ejecución deficientes.


Tengo una tabla de ~ 27M filas, con una columna FK no nula compatible con un índice no agrupado. La tabla está agrupada en su clave principal. Ambas columnas son varchar.

Una actualización de estadísticas de escaneo completo para nuestra columna FK proporciona un vector de densidad de aspecto normal:

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

Es decir, se espera que leamos aproximadamente 1.7 filas para cada distintivo con el que INSTANCELEMENTIDnos unimos.

Un contenedor típico del histograma se ve así:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

Sin embargo, si hacemos una actualización muestreada (usando el número de muestra predeterminado que es 230k filas para esta tabla) las cosas se vuelven extrañas:

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

La densidad INSTANCEELEMENTIDes ahora dos órdenes de magnitud mayor. (Sin embargo, la densidad para ambas columnas se ha estimado en un valor bastante aceptable).

Un contenedor típico del histograma ahora se ve así;

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

que es una distribución completamente diferente. Tenga en cuenta que INSTANCEELEMENTIDcon el mayor número de IDs asociados tiene 12, el número más común es 1. También es muy extraño que algunos contenedores obtengan EQ_ROWS = 1, esto sucede en aproximadamente el 10% de los contenedores.

No hay un sorteo "desafortunado" de filas extrañas que puedan contribuir a esto.

¿Estoy leyendo el histograma correctamente? ¿No parece que el muestreo ha escalado de alguna manera EQ_ROWS, DISTINCT_RANGE_ROWS y AVG_RANGE_ROWS incorrectamente?

La mesa está, por lo que puedo decir, descuidada. Intenté emular el muestreador estimando los valores yo mismo tablesample. Contar estos resultados de una manera normal da resultados que están de acuerdo con la versión fullscan, no con el muestreador.

Además, no he podido reproducir este comportamiento en índices agrupados.


Lo he reducido a esto para reproducirlo:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

La tabla debe ser lo suficientemente grande para que esto se observe. He visto esto con uniqueidentifery varchar(100)pero no int.

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

Compara las dos estadísticas; el que tiene muestreo ahora representa un vector de densidad totalmente diferente y los contenedores de histograma están apagados. Tenga en cuenta que la tabla no está sesgada.

El uso intcomo tipo de datos no causa esto, ¿SQL Server no examina todo el punto de datos cuando se usa varchar?

Vale la pena mencionar que el problema parece escalar, lo que ayuda a aumentar la frecuencia de muestreo.

Paul White 9
fuente

Respuestas:

3

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 StatManfunció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 StatManfunció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 VARCHARcolumnas 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 FKcolumna:

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 VARCHARcolumna 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_ROWSsea ​​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_ROWSvalores 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 FKes 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_ROWSes 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_ROWSnuevamente 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 INSTANCEELEMENTIDvalor 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, Statmaneso 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 FULLSCANy 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 FULLSCANactualizació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 FULLSCANactualizaciones de estadísticas se realizan en paralelo, por lo que una FULLSCANactualización puede finalizar más rápido que algunas actualizaciones muestreadas.

Joe Obbish
fuente
Gracias por la respuesta, Joe! Esto parece un error o falta de función; recuerde que este comportamiento no ocurre cuando usa valores basados ​​en INT. En los INT, el sistema funciona mucho mejor y se obtiene una estimación de la distribución estadística que se aproxima mucho mejor a la distribución real. Mientras que StatMan obviamente hace algunas suavizaciones / heurísticas; Diría que es bastante desconcertante que pueda obtener resultados mucho mejores usted mismo al calcular el histograma directamente, aún utilizando los mismos datos de origen que uno obtendría contablesample
@JohanBenumEvensberget IMO No es irrazonable que se comporte de manera diferente para las columnas INT. Con INT tiene un dominio mucho más limitado para los valores perdidos. Para las cadenas, realmente podría ser cualquier cosa hasta el límite de longitud. Puede ser desconcertante cuando no obtenemos un buen histograma, pero funciona bastante bien la mayor parte del tiempo. Dado que el código es secreto, realmente no podemos saber si funciona como se esperaba o no. Puede considerar hacer una publicación aquí si cree que MS debería abordar este problema: connect.microsoft.com/SQLServer/Feedback
Joe Obbish