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 :
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;