Surmontez MERGE JOIN (INDEX SCAN) avec une valeur de clé unique explicite sur une clé étrangère

9

Ajouté 7/11 Le problème est que des blocages se produisent en raison de l'analyse d'index pendant MERGE JOIN. Dans ce cas, une transaction tente d'obtenir le verrou S sur tout l'index dans la table parent FK, mais auparavant, une autre transaction place le verrou X sur une valeur clé de l'index.

Permettez-moi de commencer par un petit exemple (TSQL2012 DB de 70-461 cource utilisé):

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

Les colonnes [custid], [empid], [shipperid]sont des paramètres corelés pour en [Sales].[Customers], [HR].[Employees], [Sales].[Shippers]conséquence. Dans chaque cas, nous avons un index clusterisé sur une colonne référencée dans une table parrent.

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

J'essaie une INSERT [Sales].[Orders] SELECT ... FROMautre table appelée [Sales].[OrdersCache]qui a la même structure que les [Sales].[Orders]clés étrangères sauf. Une autre chose peut être importante pour mentionner la table [Sales].[OrdersCache]est un index clusterisé.

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

Comme prévu lorsque j'essaie d'insérer un faible volume de données, LOOP JOIN fonctionne très bien en faisant une recherche d'index sur les clés étrangères.

Avec des volumes de données élevés, MERGE JOIN est utilisé par l'optimiseur de requêtes comme un moyen le plus efficace de conserver la clé foregn dans la requête.

Et il n'y a rien à voir avec cela, sauf en utilisant OPTION (LOOP JOIN) dans notre cas avec des clés étrangères ou INNER LOOP JOIN dans le cas explicite JOIN.

Voici la requête que j'essaie d'exécuter dans mon environnement:

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

En regardant le plan, nous pouvons voir que les 3 clés étrangères validées avec MERGE JOIN. Ce n'est pas un moyen approprié pour moi car il utilise INDEX SCAN avec verrouillage de l'index entier. MERGE JOIN lors de la validation des clés étrangères

L'utilisation d'OPTION (LOOP JOIN) n'est pas appropriée car elle coûte près de 15% de plus que MERGE JOIN (je pense que la régression sera plus importante avec l'augmentation des volumes de données).

Dans l'instruction SELECT, vous pouvez voir une seule valeur d' shipperidattribut pour l'ensemble inséré. À mon avis, il doit y avoir un moyen d'accélérer la phase de validation de l'ensemble inséré au moins pour l'attribut immuable. Quelque chose comme:

  • faire LOOP JOIN, MERGE JOIN, HASH JOIN si nous avons un sous-ensemble non défini pour la validation JOIN
  • s'il n'y a qu'une seule valeur explicite de la colonne validée, nous ne faisons la validation qu'une seule fois (INDEX SEEK).

Existe-t-il un modèle commun pour contourner la situation ci-dessus en utilisant des structures de code, des objets DDL supplémentaires, etc.?

Ajouté le 20/07. Solution. Query Optimizer effectue déjà une optimisation de validation «clé unique - clé étrangère» en utilisant MERGE JOIN. Et fait uniquement pour la table Sales.Shippers, laissant LOOP JOIN pour une autre jointure dans la requête en même temps. Étant donné que j'ai quelques lignes dans la table parent, l'Optimiseur de requête utilise l'algorithme de jointure Tri-fusion et compare chaque ligne de la table interne avec la table parent une seule fois. C'est donc la réponse à ma question s'il existe un mécanisme particulier pour traiter efficacement les valeurs uniques dans un ensemble lors de la validation de clé unique. Ce n'est pas une décision si parfaite, mais c'est ainsi que SQL Server optimise le cas.

L'enquête sur les performances a révélé que dans mon cas, l'instruction d'insertion MERGE JOIN et LOOP JOIN est devenue approximativement égale à 750 lignes insérées simultanément avec la supériorité suivante de MERGE JOIN (en ressource de temps CPU). L'utilisation d'OPTION (LOOP JOIN) est donc une solution appropriée pour mon processus métier.

Oleg I
la source

Réponses:

8

L'utilisation d'OPTION (LOOP JOIN) n'est pas appropriée car elle coûte près de 15% de plus que MERGE JOIN

Les pourcentages de coût affichés dans la sortie de showplan sont toujours des estimations de modèle d'optimiseur, même dans un plan (réel) post-exécution. Ces coûts ne reflètent probablement pas les performances d'exécution réelles sur votre matériel particulier. La seule façon d'être sûr est de tester les alternatives avec votre charge de travail, en mesurant les mesures les plus importantes pour vous (temps écoulé, utilisation du processeur, etc.).

D'après mon expérience, l'optimiseur passe de la jointure de boucles à la fusion de jointures pour la validation de clé étrangère beaucoup trop tôt. Dans toutes les opérations, sauf les plus importantes, j'ai trouvé que les boucles jointes étaient préférables. Et par «grand», j'entends au moins des dizaines de millions de lignes, certainement pas le millier que vous indiquez dans les commentaires de votre question.

À mon avis, il doit y avoir un moyen d'accélérer la phase de validation de l'ensemble inséré au moins pour l'attribut immuable.

Cela est logique en principe, mais il n'y a pas une telle logique aujourd'hui dans la logique de construction de plan que la validation de clé étrangère utilise. La logique actuelle est délibérément très générique et orthogonale à la requête plus large; des optimisations spécifiques compliquent les tests et augmentent la probabilité de bogues de bord.

Existe-t-il un modèle commun pour contourner la situation ci-dessus en utilisant des structures de code, des objets DDL supplémentaires, etc.?

Pas que je sache. Le risque de blocage avec la validation de la clé étrangère de fusion et de jointure est bien connu , la solution de contournement la plus utilisée étant l' OPTION (LOOP JOIN)indice. Il n'y a aucun moyen d'éviter que des verrous partagés ne soient pris pendant la validation de la clé étrangère, car ils sont nécessaires pour l'exactitude , même sous des niveaux d'isolement de version de ligne.

Il n'y a pas de meilleure réponse générale (que l'indice de jointure de boucle) si vous souhaitez conserver la possibilité pour plusieurs processus simultanés d'ajouter des lignes aux tables parent et enfant de manière transactionnelle. Mais, si vous souhaitez sérialiser les modifications de données (pas les lectures), l'utilisation de sp_getapplock est une technique fiable et simple.

Paul White 9
la source