DROP TABLE IF EXISTS #EmptyTable, #BigTable
CREATE TABLE #EmptyTable(A int);
CREATE TABLE #BigTable(A int);
INSERT INTO #BigTable
SELECT TOP 10000000 CRYPT_GEN_RANDOM(3)
FROM sys.all_objects o1,
sys.all_objects o2,
sys.all_objects o3;
WITH agg
AS (SELECT DISTINCT a
FROM #BigTable)
SELECT *
FROM #EmptyTable E
INNER HASH JOIN agg B
ON B.A = E.A;
Il s'agit d'une repro simplifiée pour un phénomène que je n'avais pas remarqué avant aujourd'hui. Mon attente pour une jointure de hachage interne serait que si l'entrée de génération est vide, le côté sonde ne devrait pas être exécuté car la jointure ne peut retourner aucune ligne. L'exemple ci-dessus contredit cela et lit les 10 millions de lignes du tableau. Cela ajoute 2,196 secondes au temps d'exécution de la requête (99,9%).
OPTION (MAXDOP 1)
le plan d'exécution ne lit aucune ligne de #BigTable
. ActualExecutions
est 0
Pour tous les opérateurs à l'intérieur de la jointure de hachage.SELECT * FROM #EmptyTable E INNER HASH JOIN #BigTable B ON B.A = E.A
- J'obtiens un plan parallèle, l'opérateur de scan à l'intérieur de la jointure de hachage a ActualExecutions
de DOP mais toujours aucune ligne n'est lue. Ce plan n'a aucun opérateur de flux de répartition (ou agrégat)Que se passe t-il ici? Pourquoi le plan d'origine présente-t-il le problème et les autres cas ne le font pas?
Ne pas exécuter le côté sonde de la jointure lorsque la génération est vide est une optimisation. Il n'est pas disponible pour jointure de hachage en mode ligne parallèle lorsque le côté sonde a une branche enfant, c'est-à-dire lorsqu'il y a un opérateur d'échange.
Il y a plusieurs années, Adam Machanic a publié un rapport similaire sur le site de commentaires Connect, aujourd'hui disparu. Le scénario était un filtre de démarrage côté sonde, qui exécutait ses opérateurs enfants de manière inattendue. La réponse de Microsoft a été que le moteur nécessite une garantie que certaines structures sont initialisées et que la seule manière saine de l'imposer était de s'assurer que les opérateurs côté sonde sont ouverts.
Si je me souviens bien des détails, le fait de ne pas initialiser une sous-arborescence a entraîné des bogues de synchronisation parallèles difficiles à corriger. S'assurer que la branche enfant a démarré a été une solution à ces problèmes.
La jointure par hachage en mode batch n'a pas cet effet secondaire car la façon dont les threads sont gérés est différente.
Dans votre cas particulier, l'effet est plus prononcé car l'agrégat de hachage est bloquant; il consomme l'intégralité de son entrée pendant l'appel Open () de l'itérateur. Lorsqu'il n'y a que des opérateurs de streaming du côté sonde, l'impact sur les performances sera souvent plus limité, selon la quantité de travail requise pour renvoyer la première ligne du côté sonde de la jointure de hachage.
Pas une réponse, mais si la jointure par hachage n'est pas forcée, cette requête n'obtiendra pas la jointure par hachage en tant que plan. Une solution de contournement consiste à définir une variable de bit à 1 si des lignes existent dans la table et à 0 sinon et au lieu d'utiliser #Emptytable (sélectionnez * dans #Emptytable où @bit = 1)
Et ajoutez une option recompiler à la fin, aucune exécution n'aura lieu.
Je pense que cette condition ne devrait jamais se produire si le forçage n'est pas utilisé et si le forçage nécessaire existe une solution de contournement.