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 id
valor 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:
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 date
columna. ¿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 Anonymised
columna a su índice, pero ese índice deberá actualizarse durante su UPDATE
consulta. 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 Update
operador. 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:
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 Update
operador 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 Update
operador. 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:
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.