Columnas dispersas, tiempo de CPU e índices filtrados

10

Sparring

Al hacer algunas pruebas en columnas dispersas, como lo hace, hubo un retroceso en el rendimiento del que me gustaría saber la causa directa.

DDL

Creé dos tablas idénticas, una con 4 columnas dispersas y otra sin columnas dispersas.

--Non Sparse columns table & NC index
CREATE TABLE dbo.nonsparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) NULL,
                      varcharval varchar(20) NULL,
                      intval int NULL,
                      bigintval bigint NULL
                      );
CREATE INDEX IX_Nonsparse_intval_varcharval
ON dbo.nonsparse(intval,varcharval)
INCLUDE(bigintval,charval);

-- sparse columns table & NC index

CREATE TABLE dbo.sparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) SPARSE NULL ,
                      varcharval varchar(20) SPARSE NULL,
                      intval int SPARSE NULL,
                      bigintval bigint SPARSE NULL
                      );

CREATE INDEX IX_sparse_intval_varcharval
ON dbo.sparse(intval,varcharval)
INCLUDE(bigintval,charval);

DML

Luego inserté unos 2540 valores NO NULOS en ambos.

INSERT INTO dbo.nonsparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;

Luego, inserté 1M valores NULL en ambas tablas

INSERT INTO dbo.nonsparse WITH(TABLOCK)  (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;

Consultas

Ejecución de tabla no dispersa

Al ejecutar esta consulta dos veces en la tabla no dispersa recién creada:

SET STATISTICS IO, TIME ON;
SELECT  * FROM dbo.nonsparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);

Las lecturas lógicas muestran 5257 páginas.

(1002540 rows affected)
Table 'nonsparse'. Scan count 1, logical reads 5257, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Y el tiempo de CPU es de 343 ms

 SQL Server Execution Times:
   CPU time = 343 ms,  elapsed time = 3850 ms.

ejecución de tabla dispersa

Ejecutando la misma consulta dos veces en la tabla dispersa:

SELECT  * FROM dbo.sparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);

Las lecturas son más bajas, 1763.

(1002540 rows affected)
Table 'sparse'. Scan count 1, logical reads 1763, physical reads 3, read-ahead reads 1759, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Pero el tiempo de CPU es mayor, 547 ms .

 SQL Server Execution Times:
   CPU time = 547 ms,  elapsed time = 2406 ms.

Plan de ejecución de tabla dispersa

plan de ejecución de tabla no dispersa


Preguntas

Pregunta original

Dado que los valores NULL no se almacenan directamente en las columnas dispersas, ¿podría el aumento en el tiempo de CPU deberse a devolver los valores NULL como un conjunto de resultados? ¿O es simplemente el comportamiento como se señala en la documentación ?

Las columnas dispersas reducen los requisitos de espacio para valores nulos a costa de más gastos generales para recuperar valores no nulos

¿O la sobrecarga solo está relacionada con las lecturas y el almacenamiento utilizado?

Incluso cuando se ejecutan ssms con los resultados de descarte después de la opción de ejecución, el tiempo de CPU de la selección dispersa fue mayor (407 ms) en comparación con el no disperso (219 ms).

EDITAR

Podría haber sido la sobrecarga de los valores no nulos, incluso si solo hay 2540 presentes, pero todavía no estoy convencido.

Esto parece tener el mismo rendimiento, pero se perdió el factor escaso.

CREATE INDEX IX_Filtered
ON dbo.sparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;

CREATE INDEX IX_Filtered
ON dbo.nonsparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;


    SET STATISTICS IO, TIME ON;

SELECT  charval,varcharval,intval,bigintval FROM dbo.sparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND  varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);


SELECT  charval,varcharval,intval,bigintval 
FROM dbo.nonsparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND 
                      varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);

Parece tener aproximadamente el mismo tiempo de ejecución:

 SQL Server Execution Times:
   CPU time = 297 ms,  elapsed time = 292 ms.

 SQL Server Execution Times:
   CPU time = 281 ms,  elapsed time = 319 ms.

Pero, ¿por qué las lecturas lógicas son la misma cantidad ahora? ¿No debería el índice filtrado para la columna dispersa no almacenar nada excepto el campo ID incluido y algunas otras páginas sin datos?

Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785

Y el tamaño de ambos índices:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

¿Por qué son del mismo tamaño? ¿Se perdió la escasez?

Ambos planes de consulta cuando se utiliza el índice filtrado


Información extra

select @@version

Microsoft SQL Server 2017 (RTM-CU16) (KB4508218) - 14.0.3223.3 (X64) 12 de julio de 2019 17:43:08 Copyright (C) 2017 Microsoft Corporation Developer Edition (64 bits) en Windows Server 2012 R2 Datacenter 6.3 (Build 9600:) (hipervisor)

Mientras ejecuta las consultas y solo selecciona el campo ID , el tiempo de CPU es comparable, con lecturas lógicas más bajas para la tabla dispersa.

Tamaño de las mesas

SchemaName  TableName   RowCounts   Used_MB Unused_MB   Total_MB
dbo         nonsparse   1002540     89.54   0.10        89.64
dbo         sparse      1002540     27.95   0.20        28.14

Al forzar el índice agrupado o no agrupado, la diferencia de tiempo de la CPU permanece.

Randi Vertongen
fuente
1
¿Podría obtener los planes para la consulta posterior a la edición?
George.Palacios
1
@ George.Palacios los agregó :)
Randi Vertongen el

Respuestas:

6

¿O es simplemente el comportamiento como se señala en la documentación?

Parece tan. La "sobrecarga" mencionada en la documentación parece ser sobrecarga de la CPU.

Perfilando las dos consultas, la consulta dispersa muestreó 367 ms de CPU, mientras que la no dispersa tenía 284 ms de CPU. Esa es una diferencia de 83 ms.

captura de pantalla de Perfview que muestra la CPU total para el hilo que ejecutó la consulta

¿Dónde está la mayor parte de eso?

Ambos perfiles se ven muy similares hasta que llegan sqlmin!IndexDataSetSession::GetNextRowValuesInternal. En ese punto, el código disperso desciende por una ruta que se ejecuta sqlmin!IndexDataSetSession::GetDataLong, que llama a algunas funciones que parecen estar relacionadas con la función de columna dispersa ( HasSparseVector, StoreColumnValue), y suman (42 + 11 =) 53 ms.

captura de pantalla de la diferencia de CPU para columna dispersa

¿Por qué son del mismo tamaño? ¿Se perdió la escasez?

Sí, parece que la optimización de almacenamiento disperso no se transfiere a índices no agrupados cuando la columna dispersa se usa como clave de índice. Por lo tanto, las columnas de clave de índice no agrupadas ocupan su tamaño completo independientemente de la escasez, pero las columnas incluidas ocupan espacio cero si son dispersas y NULL.

Mirando el DBCC PAGEresultado de una página de índice agrupada con columnas dispersas de valor NULL, puedo ver que la longitud del registro es 11 (4 para el ID + 7 para la sobrecarga estándar por registro):

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 11

Para el índice filtrado, el registro siempre es 40, que es la suma del tamaño de todas las columnas clave (4 bytes ID + 20 bytes charval + 4 bytes varcharval + 4 bytes intval + 8 bytes big intval = 40 bytes).

Por alguna razón, DBCC PAGEno incluye la sobrecarga de 7 bytes en "Tamaño de registro" para registros de índice:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 40

El tamaño del índice no filtrado es menor (4 bytes ID + 4 bytes intval + 4 bytes varcharval = 12 bytes) porque dos de las columnas dispersas son columnas incluidas, lo que nuevamente obtiene la optimización de la dispersión:

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 12

Supongo que esta diferencia en el comportamiento se alinea con una de las limitaciones enumeradas en la página de documentos:

Una columna dispersa no puede ser parte de un índice agrupado o de un índice de clave principal único

Se les permite ser claves en índices no agrupados, pero no se almacenan, eh, escasamente.

Josh Darnell
fuente
2
¡Buena esa! ¡Gracias de nuevo!
Randi Vertongen