Considérez la requête suivante qui insère des lignes d'une table source uniquement si elles ne sont pas déjà dans la table cible:
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);
Une forme de plan possible comprend une jointure de fusion et une bobine désireuse. L'opérateur de bobine désireux est présent pour résoudre le problème d'Halloween :
Sur ma machine, le code ci-dessus s'exécute en environ 6900 ms. Le code de repro pour créer les tableaux est inclus au bas de la question. Si je ne suis pas satisfait des performances, je pourrais essayer de charger les lignes à insérer dans une table temporaire au lieu de compter sur la bobine désireuse. Voici une implémentation possible:
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);
Le nouveau code s'exécute en environ 4400 ms. Je peux obtenir des plans réels et utiliser Actual Time Statistics ™ pour examiner où le temps est passé au niveau de l'opérateur. Notez que demander un plan réel ajoute des frais généraux importants pour ces requêtes, de sorte que les totaux ne correspondront pas aux résultats précédents.
╔═════════════╦═════════════╦══════════════╗
║ 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 ║
╚═════════════╩═════════════╩══════════════╝
Le plan de requête avec la bobine désireuse semble passer beaucoup plus de temps sur les opérateurs d'insertion et de bobine que le plan qui utilise la table temporaire.
Pourquoi le plan avec la table temporaire est-il plus efficace? N'est-ce pas une bobine impatiente surtout juste une table temporaire interne de toute façon? Je crois que je cherche des réponses qui se concentrent sur les internes. Je peux voir comment les piles d'appels sont différentes mais je ne peux pas comprendre la situation dans son ensemble.
Je suis sur SQL Server 2017 CU 11 au cas où quelqu'un voudrait le savoir. Voici le code pour remplir les tables utilisées dans les requêtes ci-dessus:
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;