¿Por qué una mesa temporal es una solución más eficiente para el problema de Halloween que una bobina ansiosa?


14

Considere la siguiente consulta que inserta filas de una tabla de origen solo si aún no están en la tabla de destino:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

Una forma de plan posible incluye una combinación de fusión y un carrete ansioso. El ansioso operador de carrete está presente para resolver el problema de Halloween :

primer plan

En mi máquina, el código anterior se ejecuta en aproximadamente 6900 ms. El código de reproducción para crear las tablas se incluye al final de la pregunta. Si no estoy satisfecho con el rendimiento, podría intentar cargar las filas para insertarlas en una tabla temporal en lugar de confiar en el ansioso carrete. Aquí hay una posible implementación:

DROP TABLE IF EXISTS #CONSULTANT_RECOMMENDED_TEMP_TABLE;
CREATE TABLE #CONSULTANT_RECOMMENDED_TEMP_TABLE (
    ID BIGINT,
    PRIMARY KEY (ID)
);

INSERT INTO #CONSULTANT_RECOMMENDED_TEMP_TABLE WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1);

El nuevo código se ejecuta en aproximadamente 4400 ms. Puedo obtener planes reales y usar Actual Time Statistics ™ para examinar dónde se pasa el tiempo a nivel del operador. Tenga en cuenta que solicitar un plan real agrega una sobrecarga significativa para estas consultas, por lo que los totales no coincidirán con los resultados anteriores.

╔═════════════╦═════════════╦══════════════╗
  operator    first query  second query 
╠═════════════╬═════════════╬══════════════╣
 big scan     1771         1744         
 little scan  163          166          
 sort         531          530          
 merge join   709          669          
 spool        3202         N/A          
 temp insert  N/A          422          
 temp scan    N/A          187          
 insert       3122         1545         
╚═════════════╩═════════════╩══════════════╝

El plan de consulta con el spool ansioso parece pasar significativamente más tiempo en los operadores de inserción y spool en comparación con el plan que usa la tabla temporal.

¿Por qué el plan con la tabla temporal es más eficiente? ¿No es un carrete ansioso principalmente una tabla temporal interna de todos modos? Creo que estoy buscando respuestas que se centren en aspectos internos. Puedo ver cómo las pilas de llamadas son diferentes, pero no puedo entender el panorama general.

Estoy en SQL Server 2017 CU 11 en caso de que alguien quiera saber. Aquí hay un código para completar las tablas utilizadas en las consultas anteriores:

DROP TABLE IF EXISTS dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR;

CREATE TABLE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR (
ID BIGINT NOT NULL,
PRIMARY KEY (ID)
);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (20000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.A_HEAP_OF_MOSTLY_NEW_ROWS;

CREATE TABLE dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (
ID BIGINT NOT NULL
);

INSERT INTO dbo.A_HEAP_OF_MOSTLY_NEW_ROWS WITH (TABLOCK)
SELECT TOP (1900000) 19999999 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Respuestas:


14

Esto es lo que llamo Manual Halloween Protection .

Puede encontrar un ejemplo de su uso con una declaración de actualización en mi artículo Optimización de consultas de actualización . Hay que tener un poco de cuidado para preservar la misma semántica, por ejemplo, bloqueando la tabla de destino contra todas las modificaciones concurrentes mientras se ejecutan las consultas separadas, si eso es relevante en su escenario.

¿Por qué el plan con la tabla temporal es más eficiente? ¿No es un carrete ansioso principalmente una tabla temporal interna de todos modos?

Una bobina tiene algunas de las características de una tabla temporal, pero las dos no son equivalentes exactos. En particular, un carrete es esencialmente una inserción desordenada fila por fila a una estructura de árbol b . Se beneficia de las optimizaciones de bloqueo y registro, pero no admite optimizaciones de carga masiva .

En consecuencia, a menudo se puede obtener un mejor rendimiento dividiendo la consulta de una manera natural: carga masiva de las nuevas filas en una tabla o variable temporal, y luego realiza una inserción optimizada (sin protección explícita de Halloween) desde el objeto temporal.

Hacer esta separación también le permite libertad adicional para ajustar las partes de lectura y escritura de la declaración original por separado.

Como nota al margen, es interesante pensar en cómo se podría abordar el problema de Halloween usando versiones de fila. Quizás una versión futura de SQL Server proporcionará esa característica en circunstancias adecuadas.


Como Michael Kutz aludió en un comentario, también podría explorar la posibilidad de explotar la optimización de llenado de agujeros para evitar HP explícito. Una forma de lograr esto para la demostración es crear un índice único (agrupado si lo desea) en la IDcolumna de A_HEAP_OF_MOSTLY_NEW_ROWS.

CREATE UNIQUE INDEX i ON dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (ID);

Con esa garantía, el optimizador puede usar el relleno de agujeros y el intercambio de conjuntos de filas:

MERGE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (SERIALIZABLE) AS HICETY
USING dbo.A_HEAP_OF_MOSTLY_NEW_ROWS AS AHOMNR
    ON AHOMNR.ID = HICETY.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID) VALUES (AHOMNR.ID);

Plan de fusión

Si bien es interesante, aún podrá lograr un mejor rendimiento en muchos casos empleando la Protección manual de Halloween implementada cuidadosamente.


5

Para ampliar un poco la respuesta de Paul, parte de la diferencia en el tiempo transcurrido entre los enfoques de spool y de tabla temporal parece deberse a la falta de apoyo para la DML Request Sortopción en el plan de spool. Con el indicador de traza no documentado 8795, el tiempo transcurrido para el enfoque de la tabla temporal salta de 4400 ms a 5600 ms.

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1, QUERYTRACEON 8795);

Tenga en cuenta que esto no es exactamente equivalente a la inserción realizada por el plan de spool. Esta consulta escribe significativamente más datos en el registro de transacciones.

El mismo efecto se puede ver a la inversa con algunos trucos. Es posible alentar a SQL Server a usar una especie en lugar de un carrete para la Protección de Halloween. Una implementación:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (987654321) 
maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
ORDER BY maybe_new_rows.ID, maybe_new_rows.ID + 1
OPTION (MAXDOP 1, QUERYTRACEON 7470, MERGE JOIN);

Ahora el plan tiene un operador TOP N Sort en lugar del carrete. El tipo es un operador de bloqueo, por lo que el carrete ya no es necesario:

ingrese la descripción de la imagen aquí

Más importante aún, ahora tenemos soporte para la DML Request Sortopción. Mirando las estadísticas de tiempo real nuevamente, el operador de inserción ahora solo toma 1623 ms. El plan completo tarda unos 5400 ms en ejecutarse sin solicitar un plan real.

Como explica Hugo , el operador Eager Spool conserva el orden. Eso se puede ver más fácilmente con un TOP PERCENTplan. Es lamentable que la consulta original con el spool no pueda aprovechar mejor la naturaleza ordenada de los datos en el spool.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.