Database
 sql >> Base de données >  >> RDS >> Database

Objectifs de ligne, partie 2 :semi-jointures

Cet article fait partie d'une série d'articles sur les objectifs de ligne. Vous pouvez trouver la première partie ici :

  • Partie 1 :Définir et identifier des objectifs de ligne

Il est relativement bien connu que l'utilisation de TOP ou un FAST n L'indicateur de requête peut définir un objectif de ligne dans un plan d'exécution (voir Définition et identification des objectifs de ligne dans les plans d'exécution si vous avez besoin d'un rappel sur les objectifs de ligne et leurs causes). Il est plutôt moins communément apprécié que les semi-jointures (et les anti-jointures) puissent également introduire un objectif de ligne, bien que ce soit un peu moins probable que ce n'est le cas pour TOP , FAST , et SET ROWCOUNT .

Cet article vous aidera à comprendre quand et pourquoi une semi-jointure invoque la logique d'objectif de ligne de l'optimiseur.

Semi jointures

Une semi-jointure renvoie une ligne à partir d'une entrée de jointure (A) s'il y en a au moins une ligne correspondante sur l'autre entrée de jointure (B).

Les différences essentielles entre une semi-jointure et une jointure régulière sont :

  • La semi jointure renvoie soit chaque ligne de l'entrée A, soit elle ne le fait pas. Aucune duplication de ligne ne peut se produire.
  • La jointure régulière duplique les lignes s'il existe plusieurs correspondances sur le prédicat de jointure.
  • La semi-jointure est définie pour renvoyer uniquement les colonnes de l'entrée A.
  • La jointure régulière peut renvoyer des colonnes à partir de l'une ou l'autre (ou des deux) entrées de jointure.

T-SQL ne prend actuellement pas en charge la syntaxe directe telle que FROM A SEMI JOIN B ON A.x = B.y , nous devons donc utiliser des formes indirectes comme EXISTS , SOME/ANY (y compris le raccourci équivalent IN pour les comparaisons d'égalité), et définissez INTERSECT .

La description d'une semi-jointure ci-dessus fait naturellement allusion à l'application d'un objectif de ligne, puisque nous sommes intéressés à trouver toute ligne correspondante en B, pas toutes ces lignes . Néanmoins, une semi-jointure logique exprimée en T-SQL peut ne pas conduire à un plan d'exécution utilisant un objectif de ligne pour plusieurs raisons, que nous détaillerons ensuite.

Transformation et simplification

Une semi-jointure logique peut être simplifiée ou remplacée par autre chose lors de la compilation et de l'optimisation de la requête. L'exemple AdventureWorks ci-dessous montre qu'une semi-jointure est entièrement supprimée, en raison d'une relation de clé étrangère de confiance :

SELECT TH.ProductID FROM Production.TransactionHistory AS THWHERE TH.ProductID IN( SELECT P.ProductID FROM Production.Product AS P);

La clé étrangère garantit que Product des lignes existeront toujours pour chaque ligne de l'historique. Par conséquent, le plan d'exécution accède uniquement à TransactionHistory tableau :

Un exemple plus courant est celui où la semi-jointure peut être transformée en jointure interne. Par exemple :

SELECT P.ProductID FROM Production.Product AS P WHERE EXISTS( SELECT * FROM Production.ProductInventory AS INV WHERE INV.ProductID =P.ProductID);

Le plan d'exécution montre que l'optimiseur a introduit un agrégat (regroupement sur INV.ProductID ) pour s'assurer que la jointure interne ne peut renvoyer que Product lignes une fois, ou pas du tout (comme requis pour préserver la sémantique de la semi-jointure) :

La transformation en jointure interne est explorée tôt car l'optimiseur connaît plus d'astuces pour les équijointures internes que pour les semi-jointures, ce qui peut conduire à davantage d'opportunités d'optimisation. Naturellement, le choix final du plan est toujours une décision basée sur les coûts parmi les alternatives explorées.

Premières optimisations

Bien que T-SQL manque de SEMI JOIN direct syntaxe, l'optimiseur sait tout sur les semi-jointures nativement et peut les manipuler directement. Les syntaxes courantes de semi-jointure de contournement sont transformées en une "véritable" semi-jointure interne au début du processus de compilation de la requête (bien avant même qu'un plan trivial ne soit envisagé).

Les deux principaux groupes de syntaxe de contournement sont EXISTS/INTERSECT , et ANY/SOME/IN . Le EXISTS et INTERSECT les cas ne diffèrent que par le fait que ce dernier est accompagné d'un DISTINCT implicite (regroupement sur toutes les colonnes projetées). Les deux EXISTS et INTERSECT sont analysés comme un EXISTS avec sous-requête corrélée. Le ANY/SOME/IN les représentations sont toutes interprétées comme une opération SOME. Nous pouvons explorer tôt cette activité d'optimisation avec quelques indicateurs de trace non documentés, qui envoient des informations sur l'activité de l'optimiseur à l'onglet des messages SSMS.

Par exemple, la semi-jointure que nous avons utilisée jusqu'à présent peut également être écrite en utilisant IN :

SELECT P.ProductIDFROM Production.Product AS PWHERE P.ProductID IN /* ou =ANY/SOME */( SELECT TH.ProductID FROM Production.TransactionHistory AS TH)OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606, QUERYTRACEON 8621); 

L'arborescence des entrées de l'optimiseur est la suivante :

L'opérateur scalaire ScaOp_SomeComp est le SOME comparaison mentionnée juste au-dessus. Le 2 est le code d'un test d'égalité, puisque IN est équivalent à = SOME . Si cela vous intéresse, il existe des codes de 1 à 6 représentant respectivement les opérateurs de comparaison (<, =, <=,>, !=,>=).

Retour à EXISTS syntaxe que je préfère utiliser le plus souvent pour exprimer une semi jointure indirectement :

SELECT P.ProductIDFROM Production.Product AS PWHERE EXISTS( SELECT * FROM Production.TransactionHistory AS TH WHERE TH.ProductID =P.ProductID)OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606, QUERYTRACEON 8621);

L'arborescence des entrées de l'optimiseur est :

Cet arbre est une traduction assez directe du texte de la requête; notez cependant que le SELECT * a déjà été remplacé par une projection de la valeur entière constante 1 (voir l'avant-dernière ligne de texte).

La prochaine chose que fait l'optimiseur est de désimbriquer la sous-requête dans la sélection relationnelle (=filtre) en utilisant la règle RemoveSubqInSel . L'optimiseur le fait toujours, puisqu'il ne peut pas agir directement sur les sous-requêtes. Le résultat est une application (alias jointure corrélée ou latérale) :

(La même règle de suppression de sous-requête produit la même sortie pour le SOME arbre d'entrée également).

L'étape suivante consiste à réécrire l'application en tant que jointure normale à l'aide de ApplyHandler famille de règles. C'est quelque chose que l'optimiseur essaie toujours de faire, car il a plus de règles d'exploration pour les jointures que pour les applications. Toutes les applications ne peuvent pas être réécrites en tant que jointure, mais l'exemple actuel est simple et réussi :

Notez que le type de jointure est semi-gauche. En effet, c'est exactement le même arbre que nous obtiendrions immédiatement si T-SQL supportait une syntaxe comme :

SELECT P.ProductID FROM Production.Product AS P LEFT SEMI JOIN Production.TransactionHistory AS TH ON TH.ProductID =P.ProductID ;

Ce serait bien de pouvoir exprimer des requêtes plus directement comme celle-ci. Quoi qu'il en soit, le lecteur intéressé est encouragé à explorer les activités de simplification ci-dessus avec d'autres manières logiquement équivalentes d'écrire cette semi-jointure en T-SQL.

Le point important à retenir à ce stade est que l'optimiseur supprime toujours les sous-requêtes , en les remplaçant par un appliquer. Il essaie ensuite de réécrire l'application comme une jointure régulière pour maximiser les chances de trouver un bon plan. Rappelez-vous que tout ce qui précède a lieu avant même qu'un plan trivial ne soit envisagé. Lors de l'optimisation basée sur les coûts, l'optimiseur peut également envisager de retransformer la jointure en application.

Hash and Merge Semi Join

SQL Server dispose de trois principales options d'implémentation physique disponibles pour une semi-jointure logique. Tant qu'un prédicat d'équijointure est présent, les jointures de hachage et de fusion sont disponibles ; les deux peuvent fonctionner en modes de semi-jointure gauche et droite. La jointure de boucles imbriquées ne prend en charge que la semi-jointure gauche (et non droite), mais ne nécessite pas de prédicat d'équijointure. Examinons les options physiques de hachage et de fusion pour notre exemple de requête (écrite cette fois sous la forme d'une intersection d'ensemble) :

SELECT P.ProductID FROM Production.Product AS PINTERSECTSELECT TH.ProductID FROM Production.TransactionHistory AS TH ;

L'optimiseur peut trouver un plan pour les quatre combinaisons de semi-jointure (gauche/droite) et (hachage/fusion) pour cette requête :

Il convient de mentionner brièvement pourquoi l'optimiseur peut prendre en compte les semi-jointures gauche et droite pour chaque type de jointure. Pour la semi-jointure par hachage, une considération de coût majeure est la taille estimée de la table de hachage, qui est toujours l'entrée gauche (supérieure) initialement. Pour la fusion semi-jointure, les propriétés de chaque entrée déterminent si une fusion un-à-plusieurs ou moins efficace plusieurs-à-plusieurs avec table de travail sera utilisée.

Il peut ressortir des plans d'exécution ci-dessus que ni le hachage ni la semi-jointure de fusion ne bénéficieraient de la définition d'un objectif de ligne . Les deux types de jointure testent toujours le prédicat de jointure au niveau de la jointure elle-même et visent à consommer toutes les lignes des deux entrées pour renvoyer un ensemble de résultats complet. Cela ne veut pas dire que les optimisations de performances n'existent pas pour les jointures de hachage et de fusion en général - par exemple, les deux peuvent utiliser des bitmaps pour réduire le nombre de lignes atteignant la jointure. Le fait est plutôt qu'un objectif de ligne sur l'une ou l'autre des entrées ne rendrait pas plus efficace une semi-jointure de hachage ou de fusion.

Boucles imbriquées et appliquer une semi-jointure

Le type de jointure physique restant est constitué de boucles imbriquées, qui se déclinent en deux versions :boucles imbriquées régulières (non corrélées) et appliquer boucles imbriquées (parfois aussi appelées boucles corrélées ou latéral rejoindre).

La jointure de boucles imbriquées régulières est similaire à la jointure de hachage et de fusion en ce sens que le prédicat de jointure est évalué à la jointure. Comme précédemment, cela signifie qu'il n'y a aucune valeur à définir un objectif de ligne sur l'une ou l'autre des entrées. L'entrée gauche (supérieure) sera toujours entièrement consommée à terme, et l'entrée interne n'a aucun moyen de déterminer quelle(s) ligne(s) doit être priorisée, car nous ne pouvons pas savoir si une ligne se joindra ou non tant que le prédicat n'aura pas été testé à la jointure. .

En revanche, une jointure Appliquer des boucles imbriquées a une ou plusieurs références externes (paramètres corrélés) à la jointure, avec le prédicat de jointure poussé vers le bas le côté intérieur (inférieur) de la jointure. Cela crée une opportunité pour l'application utile d'un objectif de ligne. Rappelez-vous qu'une semi-jointure nous oblige uniquement à vérifier l'existence d'une ligne sur l'entrée de jointure B qui correspond à la ligne actuelle sur l'entrée de jointure A (en pensant uniquement aux stratégies de jointure de boucles imbriquées maintenant).

En d'autres termes, à chaque itération d'une application, nous pouvons arrêter de regarder l'entrée B dès que la première correspondance est trouvée, en utilisant le prédicat de jointure poussé vers le bas. C'est exactement le genre de choses pour lesquelles un objectif de ligne est bon :générer une partie d'un plan optimisé pour renvoyer rapidement les n premières lignes correspondantes (où n = 1 ici).

Bien sûr, un objectif en ligne peut être une bonne chose ou non, selon les circonstances. Il n'y a rien de spécial à propos de l'objectif de semi-jointure à cet égard. Considérez une situation où le côté interne de la semi-jointure est plus complexe qu'un simple accès à une table simple, peut-être une jointure multi-tables. La définition d'un objectif de ligne peut aider l'optimiseur à sélectionner une stratégie de navigation efficace pour cette sous-arborescence particulière uniquement , en trouvant la première ligne correspondante pour satisfaire la semi-jointure via des jointures de boucles imbriquées et des recherches d'index. Sans l'objectif de ligne, l'optimiseur peut naturellement choisir des jointures de hachage ou de fusion avec des tris pour minimiser le coût attendu du renvoi de toutes les lignes possibles. Notez qu'il y a une hypothèse ici, à savoir que les gens écrivent généralement des semi-jointures en s'attendant à ce qu'une ligne correspondant à la condition de recherche existe en fait. Cela me semble une hypothèse assez juste.

Quoi qu'il en soit, le point important à ce stade est :Seulement appliquer la jointure de boucles imbriquées a un objectif de ligne appliqué par l'optimiseur (rappelez-vous cependant qu'un objectif de ligne pour appliquer la jointure de boucles imbriquées n'est ajouté que si l'objectif de ligne est inférieur à l'estimation sans lui). Nous examinerons quelques exemples pratiques pour, espérons-le, clarifier tout cela ensuite.

Exemples de semi-jointure de boucles imbriquées

Le script suivant crée deux tables temporaires de tas. Le premier a des nombres de 1 à 20 inclus; l'autre a 10 copies de chaque numéro du premier tableau :

DOP TABLE SI EXISTE #E1, #E2 ; CREATE TABLE #E1 (c1 entier NULL);CREATE TABLE #E2 (c1 entier NULL); INSERT #E1 (c1)SELECT SV.numberFROM master.dbo.spt_values ​​AS SVWHERE SV.[type] =N'P' AND SV.number>=1 AND SV.number <=20 ; INSERT #E2 (c1)SELECT (SV.number % 20) + 1FROM master.dbo.spt_values ​​AS SVWHERE SV.[type] =N'P' AND SV.number>=1 AND SV.number <=200; 

En l'absence d'index et d'un nombre relativement faible de lignes, l'optimiseur choisit une implémentation de boucles imbriquées (plutôt que de hachage ou de fusion) pour la requête de semi-jointure suivante). Les indicateurs de trace non documentés nous permettent de voir l'arborescence de sortie de l'optimiseur et les informations d'objectif de ligne :

SELECT E1.c1 FROM #E1 AS E1WHERE E1.c1 IN (SELECT E2.c1 FROM #E2 AS E2)OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8612);

Le plan d'exécution estimé comprend une jointure de boucles imbriquées semi-jointure, avec 200 lignes par analyse complète de la table #E2 . Les 20 itérations de la boucle donnent une estimation totale de 4 000 lignes :

Les propriétés de l'opérateur de boucles imbriquées montrent que le prédicat est appliqué à la jointure ce qui signifie qu'il s'agit d'une jointure de boucles imbriquées non corrélées :

La sortie de l'indicateur de trace (sur l'onglet Messages SSMS) affiche une semi-jointure de boucles imbriquées et aucun objectif de ligne (RowGoal 0) :

Notez que le plan de post-exécution pour cette requête jouet n'affichera pas 4 000 lignes lues à partir de la table #E2 au total. La semi-jointure de boucles imbriquées (corrélée ou non) cessera de chercher plus de lignes du côté intérieur (par itération) dès que la première correspondance pour la ligne extérieure actuelle est rencontrée. Maintenant, l'ordre des lignes rencontrées à partir de l'analyse du tas de #E2 à chaque itération est non déterministe (et peut être différent à chaque itération), donc en principe la quasi-totalité des lignes pourra être testée à chaque itération, dans le cas où la ligne correspondante est rencontrée le plus tard possible (ou bien, en cas d'absence de ligne correspondante, pas du tout).

Par exemple, si nous supposons une implémentation d'exécution où les lignes sont analysées dans le même ordre (par exemple "ordre d'insertion") à chaque fois, le nombre total de lignes analysées dans cet exemple de jouet serait de 20 lignes à la première itération, 1 ligne à la deuxième itération, 2 lignes à la troisième itération, et ainsi de suite pour un total de 20 + 1 + 2 + (…) + 19 =210 lignes. En effet, vous êtes tout à fait susceptible d'observer ce total, qui en dit plus sur les limites d'un code de démonstration simple que sur toute autre chose. On ne peut pas se fier à l'ordre des lignes renvoyées par une méthode d'accès non ordonnée, pas plus qu'on ne peut se fier à la sortie apparemment ordonnée d'une requête sans un ORDER BY de niveau supérieur clause.

Appliquer la semi-jointure

Nous créons maintenant un index non clusterisé sur la plus grande table (pour encourager l'optimiseur à choisir une semi-jointure d'application) et exécutons à nouveau la requête :

CRÉER UN INDEX NON CLUSTÉRÉ nc1 SUR #E2 (c1); SELECT E1.c1 FROM #E1 AS E1WHERE E1.c1 IN (SELECT E2.c1 FROM #E2 AS E2)OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8612);

Le plan d'exécution comporte désormais une semi-jointure d'application, avec 1 ligne par recherche d'index (et 20 itérations comme auparavant) :

Nous pouvons dire qu'il s'agit d'une application de semi-jointure car les propriétés de jointure affichent une référence externe plutôt qu'un prédicat de jointure :

Le prédicat de jointure a été poussé vers le bas le côté intérieur de l'application, et mis en correspondance avec le nouvel index :

Chaque recherche est censée renvoyer 1 ligne, malgré le fait que chaque valeur est dupliquée 10 fois dans cette table ; ceci est un effet de l'objectif de ligne . L'objectif de ligne sera plus facile à identifier sur les builds SQL Server qui exposent le EstimateRowsWithoutRowGoal plan (SQL Server 2017 CU3 au moment de la rédaction). Dans une prochaine version de Plan Explorer, cela sera également exposé dans des info-bulles pour les opérateurs concernés :

La sortie de l'indicateur de trace est :

L'opérateur physique est passé d'une jointure de boucles à une application s'exécutant en mode semi-jointure gauche. Accès au tableau #E2 a acquis un objectif de ligne de 1 (la cardinalité sans l'objectif de ligne est indiquée par 10). L'objectif de ligne n'est pas un gros problème dans ce cas, car le coût de récupération d'environ dix lignes par recherche n'est pas très supérieur à celui d'une ligne. Désactivation des objectifs de ligne pour cette requête (à l'aide de l'indicateur de trace 4138 ou du DISABLE_OPTIMIZER_ROWGOAL indicateur de requête) ne changerait pas la forme du plan.

Néanmoins, dans des requêtes plus réalistes, la réduction des coûts due à l'objectif de ligne interne peut faire la différence entre les options de mise en œuvre concurrentes. Par exemple, la désactivation de l'objectif de ligne peut amener l'optimiseur à choisir une semi-jointure de hachage ou de fusion à la place, ou l'une des nombreuses autres options envisagées pour la requête. Si rien d'autre, l'objectif de ligne ici reflète précisément le fait qu'une semi-jointure d'application arrête de rechercher le côté intérieur dès que la première correspondance est trouvée et passe à la ligne extérieure suivante.

Notez que des doublons ont été créés dans la table #E2 de sorte que l'objectif d'application de ligne de semi-jointure (1) soit inférieur à l'estimation normale (10, à partir des informations de densité statistique). S'il n'y avait pas de doublons, l'estimation de ligne pour chaque recherche dans #E2 serait également 1 ligne, donc un objectif de ligne de 1 ne serait pas appliqué (rappelez-vous la règle générale à ce sujet !)

Objectifs de rangée par rapport au sommet

Étant donné que les plans d'exécution n'indiquent pas du tout la présence d'un objectif de ligne avant SQL Server 2017 CU3, on pourrait penser qu'il aurait été plus clair d'implémenter cette optimisation à l'aide d'un opérateur Top explicite, plutôt qu'une propriété cachée comme un objectif de ligne. L'idée serait de simplement placer un opérateur Top (1) sur le côté intérieur d'une semi/anti jointure d'application au lieu de définir un objectif de ligne au niveau de la jointure elle-même.

L'utilisation d'un opérateur Top de cette manière n'aurait pas été entièrement sans précédent. Par exemple, il existe déjà une version spéciale de Top connue sous le nom de top de comptage de lignes vue dans les plans d'exécution de modification de données lorsqu'un SET ROWCOUNT différent de zéro est en vigueur (notez que cette utilisation spécifique est obsolète depuis 2005 bien qu'elle soit toujours autorisée dans SQL Server 2017). L'implémentation du nombre de lignes supérieur est un peu maladroite dans la mesure où l'opérateur supérieur est toujours affiché sous la forme d'un Top (0) dans le plan d'exécution, quelle que soit la limite réelle du nombre de lignes en vigueur.

Il n'y a aucune raison impérieuse pour laquelle l'objectif d'application de la ligne de semi-jointure n'aurait pas pu être remplacé par un opérateur Top (1) explicite. Cela dit, il y a quelques raisons de préférer ne pas faire ça :

  • L'ajout d'un Top (1) explicite nécessite plus d'efforts de codage et de tests de l'optimiseur que l'ajout d'un objectif de ligne (qui est déjà utilisé pour d'autres choses).
  • Top n'est pas un opérateur relationnel ; l'optimiseur a peu de support pour raisonner à ce sujet. Cela pourrait avoir un impact négatif sur la qualité du plan en limitant la capacité de l'optimiseur à transformer des parties d'un plan de requête, par ex. en déplaçant les agrégats, les unions, les filtres et les jointures.
  • Cela introduirait un couplage étroit entre l'implémentation d'application de la semi-jointure et le sommet. Les cas spéciaux et le couplage étroit sont d'excellents moyens d'introduire des bogues et de rendre les modifications futures plus difficiles et sujettes aux erreurs.
  • Le Top (1) serait logiquement redondant et présent uniquement pour son effet secondaire d'objectif de ligne.

Ce dernier point mérite d'être développé avec un exemple :

SELECT P.ProductID FROM Production.Product AS PWHERE EXISTS ( SELECT TOP (1) TH.ProductID FROM Production.TransactionHistory AS TH WHERE TH.ProductID =P.ProductID );

Le TOP (1) dans la sous-requête Existe est simplifiée par l'optimiseur, donnant un simple plan d'exécution de semi-jointure :

L'optimiseur peut également supprimer un DISTINCT redondant ou GROUP BY dans la sous-requête. Les éléments suivants produisent tous le même plan que ci-dessus :

-- Redondant DISTINCTSELECT P.ProductID FROM Production.Product AS PWHERE EXISTS ( SELECT DISTINCT TH.ProductID FROM Production.TransactionHistory AS TH WHERE TH.ProductID =P.ProductID ); -- Redondant GROUP BYSELECT P.ProductID FROM Production.Product AS PWHERE EXISTS ( SELECT TH.ProductID FROM Production.TransactionHistory AS TH WHERE TH.ProductID =P.ProductID GROUP BY TH.ProductID ); -- Redondant DISTINCT TOP (1)SELECT P.ProductID FROM Production.Product AS PWHERE EXISTS ( SELECT DISTINCT TOP (1) TH.ProductID FROM Production.TransactionHistory AS TH WHERE TH.ProductID =P.ProductID );

Résumé et réflexions finales

Seulement appliquer Les semi-jointures de boucles imbriquées peuvent avoir un objectif de ligne défini par l'optimiseur. Il s'agit du seul type de jointure qui pousse le(s) prédicat(s) de jointure vers le bas depuis la jointure, ce qui permet de tester l'existence d'une correspondance à effectuer tôt . Boucles imbriquées non corrélées semi join presque jamais* définit un objectif de ligne, et un hachage ou une fusion semi-jointure non plus. Les boucles d'application imbriquées peuvent être distinguées des boucles imbriquées non corrélées jointes par la présence de références externes (au lieu d'un prédicat) sur l'opérateur de jointure de boucles imbriquées pour une application.

Les chances de voir une semi-jointure d'application dans le plan d'exécution final dépendent quelque peu de l'activité d'optimisation précoce. En l'absence de syntaxe T-SQL directe, nous devons exprimer les semi-jointures en termes indirects. Celles-ci sont analysées dans une arborescence logique contenant une sous-requête, que l'activité précoce de l'optimiseur transforme en une application, puis en une semi-jointure non corrélée si possible.

Cette activité de simplification détermine si une semi-jointure logique est présentée à l'optimiseur basé sur les coûts comme une semi-jointure d'application ou normale. Lorsqu'il est présenté comme une application logique semi-jointure, le CBO est presque certain de produire un plan d'exécution final comportant des boucles imbriquées d'application physique (et donc de définir un objectif de ligne). Lorsqu'il est présenté avec une semi-jointure non corrélée, le CBO peut envisager la transformation en application (ou non). Le choix final du plan est une série de décisions basées sur les coûts, comme d'habitude.

Comme tous les objectifs de ligne, l'objectif de ligne semi-joindre peut être une bonne ou une mauvaise chose pour les performances. Savoir qu'une semi-jointure d'application définit un objectif de ligne aidera au moins les utilisateurs à reconnaître et à résoudre la cause si un problème devait survenir. La solution ne sera pas toujours (ou même généralement) de désactiver les objectifs de ligne pour la requête. Des améliorations de l'indexation (et/ou de la requête) peuvent souvent être apportées pour fournir un moyen efficace de localiser la première ligne correspondante.

Je couvrirai les anti-semi-jointures dans un article séparé, poursuivant la série d'objectifs en ligne.

* L'exception est une semi-jointure de boucles imbriquées non corrélées sans prédicat de jointure (ce qui est peu courant). Cela définit un objectif de ligne.