Datos inherentemente ordenados como si fuera un índice agrupado

8

Tengo la siguiente tabla con 7,5 millones de registros:

CREATE TABLE [dbo].[TestTable](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TestCol] [nvarchar](50) NOT NULL,
    [TestCol2] [nvarchar](50) NOT NULL,
    [TestCol3] [nvarchar](50) NOT NULL,
    [Anonymised] [tinyint] NOT NULL,
    [Date] [datetime] NOT NULL,
CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Noto que cuando hay un índice no agrupado en el campo de fecha:

CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date])

-y ejecuto la siguiente consulta:

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Date] <= '25 August 2016'

-los datos que devuelve la operación de acceso al índice se ordenan para que coincidan con el orden de las claves de PK / CX, lo que reduce el rendimiento.

Plan de consulta

Me sorprendió descubrir que eliminar el índice del campo de fecha en realidad mejora el rendimiento de la consulta en aproximadamente un 30% porque ya no realiza la clasificación:

Plan de consulta

Mi teoría, y esto puede ser obvio para los más experimentados entre ustedes, es que ha descubierto que la columna de fecha está ordenada implícitamente exactamente igual que la clave principal / índice agrupado.

Entonces mi pregunta es: ¿es posible aprovechar este hecho para mejorar el rendimiento de mi consulta?

AproposArmadillo
fuente
1
No he mirado los planes, pero sospecharía que el rendimiento (bueno, la duración, ninguna de estas cifras de% de costo estimado inútil) mejoró porque ya no tenía que actualizar el índice que eliminó, no debido a una operación de clasificación.
Aaron Bertrand
@AaronBertrand Puedo estar leyendo esto incorrectamente, así que corríjame si me equivoco, pero parece haber una operación de actualización de índice en ambos planes de consulta. ¿Te refieres a algo más?
AproposArmadillo
1
Una vez más, dije, no miré los planes. Usted dijo que "eliminar el índice del campo de fecha mejora el rendimiento de la consulta" ... si eliminó el índice, no debería aparecer en el plan, por lo que tal vez haya recopilado el plan incorrecto o no haya eliminado realmente el índice que creías que hiciste. Y una vez más, un porcentaje estimado de un plan es un indicador, pero en realidad no refleja la medición del rendimiento real de ninguna manera. Es una estimación que se calcula antes incluso de que se ejecute la consulta.
Aaron Bertrand
@ Aaron Bertrand, de todos modos no tenía que actualizar el índice, porque [Fecha] no estaba entre los campos actualizados.
Denis Rubashkin
1
@ Shaffanhoon ¿Has intentado recrear el índice [Date]pero en DESCorden? Simplemente curioso ya que el predicado es <=. Además, si el índice activado Date(por defecto, el ACSorden) ayuda a otras consultas, entonces ¿tal vez pueda intentar agregar una sugerencia de tabla a la ACTUALIZACIÓN para forzarlo a usar la PK? O, tal vez divida esto en dos partes: cree una tabla temporal, complete con [Id]base en [Date] <= '25 August 2016', y luego elimine la WHEREde la ACTUALIZACIÓN y agregue FROM dbo.TestTable tt INNER JOIN #tmp ids ON ids.[Id] = tt.[Id]. Después de todo, es una ACTUALIZACIÓN y necesita encontrar las filas reales, índice o no.
Solomon Rutzky

Respuestas:

7

Me burlé de los datos de prueba que reproducen principalmente su problema:

INSERT INTO [dbo].[TestTable] WITH (TABLOCK)
SELECT TOP (7000000) N'*NOT GDPR*', N'*NOT GDPR*', N'*NOT GDPR*', 0, DATEADD(DAY, q.RN  / 16965, '20160801')
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
ORDER BY q.RN
OPTION (MAXDOP 1);


DROP INDEX IF EXISTS [dbo].[TestTable].IX_TestTable_Date;
CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date]);

Estadísticas para la consulta que usa el índice no agrupado:

Tabla 'TestTable'. Cuenta de escaneo 1, lecturas lógicas 1299838, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas anticipadas 0 del lob.

Tiempos de ejecución de SQL Server: tiempo de CPU = 984 ms, tiempo transcurrido = 988 ms.

Estadísticas para la consulta que usa el índice agrupado:

Tabla 'TestTable'. Cuenta de escaneo 1, lecturas lógicas 72609, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 781 ms, tiempo transcurrido = 772 ms.

Llegando a su pregunta:

¿Es posible aprovechar este hecho para mejorar el rendimiento de mi consulta?

Si. Puede usar el índice no agrupado que ya tiene para encontrar eficientemente el idvalor máximo que necesita actualizarse. Si guarda eso en una variable y lo filtra en contra, obtendrá un plan de consulta para la actualización que realiza el análisis de índice agrupado (sin la clasificación) que se detiene antes y, por lo tanto, hace menos IO. Aquí hay una implementación:

DECLARE @Id INT;

SELECT TOP (1) @Id = Id
FROM dbo.TestTable 
WHERE [Date] <= '25 August 2016'
ORDER BY [Date] DESC, Id DESC;

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Id] < @Id AND [Date] <= '25 August 2016'
AND [Anonymised] <> 1 -- optional
OPTION (MAXDOP 1);

Ejecutar estadísticas para la nueva consulta:

Tabla 'TestTable'. Cuenta de escaneo 1, lecturas lógicas 3, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tabla 'TestTable'. Cuenta de escaneo 1, lecturas lógicas 4776, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 515 ms, tiempo transcurrido = 510 ms.

Además del plan de consulta:

ok plan de consulta

Dicho todo esto, su deseo de hacer la consulta más rápida me sugiere que planea ejecutar la consulta más de una vez. En este momento, su consulta tiene un filtro abierto en la datecolumna. ¿Es realmente necesario anonimizar las filas más de una vez? ¿Puede evitar actualizar o escanear filas que ya estaban anonimizadas? Sin duda, debería ser más rápido actualizar un rango de fechas con fechas en ambos lados. También puede agregar la Anonymisedcolumna a su índice, pero ese índice deberá actualizarse durante su UPDATEconsulta. En resumen, evite procesar los mismos datos una y otra vez si puede.

La consulta original que tiene con la ordenación es más lenta debido al trabajo realizado en el Clustered Index Updateoperador. La cantidad de tiempo dedicado a la búsqueda de índice y la clasificación es de solo 407 ms. Puedes ver esto en el plan real. El plan se ejecuta en modo de fila, por lo que el tiempo dedicado a la ordenación es el tiempo de ese operador junto con cada operador secundario:

ingrese la descripción de la imagen aquí

Eso deja al operador de clasificación con aproximadamente 1600 ms de tiempo. SQL Server necesita leer páginas del índice agrupado para realizar la actualización. Puede ver que el Clustered Index Updateoperador realiza 1205921 lecturas lógicas. Puede leer más sobre cómo ordenar las optimizaciones para DML y la captación previa optimizada en esta publicación de blog de Paul White .

El otro plan de consulta que tiene (sin la clasificación) toma 683 ms para el escaneo de índice agrupado y aproximadamente 550 ms para el Clustered Index Updateoperador. El operador de actualización no hace ninguna E / S para esta consulta.

La respuesta simple de por qué el plan con la clasificación es más lenta es que SQL Server realiza más lecturas lógicas en el índice agrupado para ese plan en comparación con el plan de exploración de índice agrupado. Incluso si todos los datos necesarios están en la memoria, todavía hay una sobrecarga y un costo para hacer esas lecturas lógicas. Es mucho más difícil obtener una mejor respuesta, ya que hasta donde yo sé, los planes no le darán más detalles. Es posible usar PerfView u otra herramienta basada en el rastreo de ETW para comparar pilas de llamadas entre las consultas:

ingrese la descripción de la imagen aquí

A la izquierda está la consulta que realiza el escaneo de índice agrupado y a la derecha está la consulta que realiza el ordenamiento. Marqué las pilas de llamadas en azul o rojo que solo aparecen en una consulta. No es sorprendente que las diferentes pilas de llamadas con un alto número de ciclos de CPU muestreados para la consulta de clasificación parezcan tener que ver con las lecturas lógicas necesarias para realizar la actualización en el índice agrupado. Además, existen diferencias en el número de ciclos muestreados entre las consultas para la misma operación. Por ejemplo, la consulta con la clasificación gasta 31 ciclos en la adquisición de bloqueos, mientras que la consulta con el escaneo solo gasta 9 ciclos en la adquisición de bloqueos.

Sospecho que SQL Server está eligiendo el plan más lento debido a una limitación de costos del operador del plan de consulta. Quizás parte de la diferencia en el tiempo de ejecución se deba al hardware o su edición de SQL Server. En cualquier caso, SQL Server no puede darse cuenta de que la columna de fecha está ordenada implícitamente exactamente igual que el índice agrupado. Los datos se devuelven del análisis de índice agrupado en orden de clave agrupado, por lo que no es necesario realizar una ordenación en un intento de optimizar IO al realizar la actualización del índice agrupado.

Joe Obbish
fuente