Blocage de mise à jour de l'index SQL Server

13

J'ai 2 requêtes qui, lorsqu'elles sont exécutées en même temps, provoquent un blocage.

Requête 1 - mettez à jour une colonne qui est incluse dans un index (index1):

update table1 set column1 = value1 where id = @Id

Prend X-Lock sur table1 puis tente un X-Lock sur index1.

Requête 2:

select columnx, columny, etc from table1 where {some condition}

Prend un S-Lock sur index1 puis tente un S-Lock sur table1.

Existe-t-il un moyen d'éviter l'impasse tout en conservant les mêmes requêtes? Par exemple, puis-je en quelque sorte prendre un X-Lock sur l'index dans la transaction de mise à jour avant la mise à jour pour m'assurer que la table et l'accès à l'index sont dans le même ordre - ce qui devrait empêcher le blocage?

Le niveau d'isolement est Lecture validée. Les verrous de ligne et de page sont activés pour les index. Il est possible que le même enregistrement participe aux deux requêtes - je ne peux pas dire à partir du graphique de blocage car il n'affiche pas les paramètres.

Graphique de blocage

Dale K
la source

Réponses:

11

Existe-t-il un moyen d'éviter l'impasse tout en conservant les mêmes requêtes?

Le graphique de blocage montre que ce blocage particulier était un blocage de conversion associé à une recherche de signet (une recherche RID dans ce cas):

Graphique de blocage

Comme le note la question, le risque de blocage général se pose car les requêtes peuvent obtenir des verrous incompatibles sur les mêmes ressources dans des ordres différents. La SELECTrequête doit accéder à l'index avant la table en raison de la recherche RID, tandis que la UPDATErequête modifie d'abord la table, puis l'index.

L'élimination de l'impasse nécessite la suppression de l'un des ingrédients de l'impasse. Voici les principales options:

  1. Évitez la recherche RID en effectuant la couverture d'index non clusterisé. Ce n'est probablement pas pratique dans votre cas car la SELECTrequête renvoie 26 colonnes.
  2. Évitez la recherche RID en créant un index clusterisé. Cela impliquerait la création d'un index clusterisé sur la colonne Proposal. Cela vaut la peine d'être considéré, bien qu'il semble que cette colonne soit de type uniqueidentifier, ce qui peut ou non être un bon choix pour un index clusterisé, en fonction de problèmes plus larges.
  3. Évitez de prendre des verrous partagés lors de la lecture en activant les options READ_COMMITTED_SNAPSHOTou de la SNAPSHOTbase de données. Cela nécessiterait des tests minutieux, en particulier en ce qui concerne tout comportement de blocage intégré. Le code de déclenchement nécessiterait également des tests pour s'assurer que la logique fonctionne correctement.
  4. Évitez de prendre des verrous partagés lors de la lecture en utilisant le READ UNCOMMITTEDniveau d'isolement de la SELECTrequête. Toutes les mises en garde habituelles s'appliquent.
  5. Évitez l'exécution simultanée des deux requêtes en question en utilisant un verrou d'application exclusif (voir sp_getapplock ).
  6. Utilisez des conseils de verrouillage de table pour éviter la concurrence. Il s'agit d'un marteau plus gros que l'option 5, car il peut affecter d'autres requêtes, pas seulement les deux identifiées dans la question.

Puis-je en quelque sorte prendre un X-Lock sur l'index dans la transaction de mise à jour avant la mise à jour pour m'assurer que la table et l'accès à l'index sont dans le même ordre

Vous pouvez essayer cela, en enveloppant la mise à jour dans une transaction explicite et en effectuant un SELECTavec un XLOCKindice sur la valeur d'index non cluster avant la mise à jour. Pour cela, vous devez savoir avec certitude quelle est la valeur actuelle de l'index non cluster, obtenir le bon plan d'exécution et anticiper correctement tous les effets secondaires de la prise de ce verrou supplémentaire. Il repose également sur le fait que le moteur de verrouillage n'est pas assez intelligent pour éviter de prendre le verrou s'il est jugé redondant .

Bref, bien que cela soit réalisable en principe, je ne le recommande pas. Il est trop facile de manquer quelque chose ou de se surpasser de manière créative. Si vous devez vraiment éviter ces blocages (plutôt que de simplement les détecter et réessayer), je vous encourage à regarder plutôt les solutions plus générales répertoriées ci-dessus.

Paul White 9
la source
Après avoir approfondi la question, je pense qu'il est préférable de la laisser inchangée. C'est un problème plus courant que j'ai réalisé à l'origine.
Dale K
1

J'ai un problème similaire qui se produit occasionnellement et voici l'approche que je prends.

  1. Ajoutez set deadlock priority low;à la sélection. Cela entraînera cette requête pour être la victime de blocage lorsqu'un blocage se produit.
  2. Configurez la logique de nouvelle tentative dans votre application pour réessayer automatiquement la sélection en cas d'échec en raison d'un blocage (ou d'un délai d'attente), après avoir attendu / mis en veille pendant une courte période de temps pour permettre aux requêtes de blocage de se terminer.

Remarque: si votre selectfait partie d'une transaction multi-instructions explicite, vous devez être sûr de réessayer la transaction entière, et pas seulement l'instruction qui a échoué, sinon vous pouvez obtenir des résultats inattendus. Si cela est un , selectalors vous êtes bien, mais si elle est la déclaration xde l' nintérieur d' une transaction, assurez - vous de toutes les nouvelle tentative des ndéclarations au cours de la nouvelle tentative.

BateTech
la source
Merci - les requêtes sont automatiquement bloquées par défaut. Et oui, nous avons déjà un mécanisme de relance robuste en place.
Dale K