Suivi, débogage et correction des conflits de verrouillage de ligne


12

En retard, j'ai été confronté à de nombreuses controverses sur le verrouillage des lignes. Le tableau litigieux semble être un tableau particulier.

C'est généralement ce qui se passe -

  • Le développeur 1 démarre une transaction à partir de l'écran frontal d'Oracle Forms
  • Le développeur 2 démarre une autre transaction, à partir d'une session différente en utilisant le même écran

~ 5 minutes plus tard, le front-end semble ne pas répondre. La vérification des sessions montre un conflit de verrouillage de ligne. La "solution" que tout le monde lance est de tuer les sessions: /

En tant que développeur de base de données

  • Que peut-on faire pour éliminer les conflits de verrouillage de ligne?
  • Serait-il possible de savoir quelle ligne d'une procédure stockée est à l'origine de ces contentions de verrouillage de ligne
  • Quelle serait la ligne directrice générale pour réduire / éviter / éliminer ces problèmes de codage?

Si cette question vous semble trop ouverte / informations insuffisantes, n'hésitez pas à modifier / faites-moi savoir - je ferai de mon mieux pour ajouter des informations supplémentaires.


Le tableau en question est sous beaucoup d'inserts et de mises à jour, je dirais que c'est l'un des tableaux les plus occupés. Le SP est assez complexe - pour simplifier - il récupère les données de différentes tables, les remplit dans des tables de travail, de nombreuses opérations arithmétiques se produisent sur la table de travail et le résultat de la table de travail est inséré / mis à jour dans la table en question.


La version de la base de données est Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit. Le flux de logique est exécuté dans le même ordre dans les deux sessions, la transaction n'est pas maintenue ouverte trop longtemps (ou du moins je pense ) et les verrous se produisent pendant l'exécution active des transactions.


Mise à jour: le nombre de lignes du tableau est plus important que prévu, à environ 3,1 millions de lignes. De plus, après avoir retracé une session, j'ai constaté que quelques instructions de mise à jour de cette table n'utilisaient pas l'index. Pourquoi en est-il ainsi - je n'en suis pas sûr. La colonne référencée dans la clause where est indexée. Je reconstruis actuellement l'index.


1
@Sathya - pouvez-vous élaborer la complexité de la procédure stockée? la table suspectée est-elle en cours de mise à jour ou d'insertion rigoureuse?
CoderHawk

Les clés étrangères jouent-elles un rôle ici (parfois cela nécessite un index) Quelle version de la base de données est en place? Le flux de la logique est-il exécuté dans le même ordre dans les deux sessions? La transaction est-elle maintenue «ouverte» pendant longtemps? Le verrouillage se produit-il pendant le temps de réflexion des utilisateurs ou pendant l'exécution active de la transaction?
ik_zelf

@ Sandy J'ai mis à jour la question
Sathyajith Bhat

@ik_zelf J'ai mis à jour la question
Sathyajith Bhat

1
Pour moi, ce n'est pas clair pourquoi c'est un problème - Oracle fait exactement ce qu'il est censé faire, c'est-à-dire sérialiser l'accès à une seule ligne. Si quelqu'un a cette ligne, vous pouvez en lire la version précédente, mais pour l'écrire, vous devez attendre qu'il libère le verrou. La seule "solution" pour cela est soit de ne pas batifoler et COMMITou ROLLBACKdans un délai raisonnable ou b) de faire en sorte que les mêmes personnes ne veuillent pas toujours la même ligne en même temps.
Gaius

Réponses:


10

Serait-il possible de savoir quelle ligne d'une procédure stockée est à l'origine de ces contentions de verrouillage de ligne?

Pas exactement, mais vous pouvez obtenir l'instruction SQL provoquant le verrouillage et à son tour identifier les lignes associées dans la procédure.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Quelle serait la ligne directrice générale pour réduire / éviter / éliminer ces problèmes de codage?

La section Oracle Concepts Guide sur les verrous indique: "Une ligne n'est verrouillée que lorsqu'elle est modifiée par un rédacteur." Une autre session mettant à jour la même ligne attendra la première session COMMITou ROLLBACKavant de continuer. Pour éliminer le problème, vous pouvez sérialiser les utilisateurs, mais voici certaines choses qui peuvent peut-être réduire le problème au niveau où il ne s'agit pas d'un problème.

  • COMMITplus souvent. Chaque version COMMITlibère des verrous, donc si vous pouvez effectuer les mises à jour par lots, la probabilité qu'une autre session ait besoin de la même ligne est réduite.
  • Assurez-vous de ne mettre à jour aucune ligne sans modifier leurs valeurs. Par exemple, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);devrait être réécrit comme le plus sélectif (lire moins de verrous) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Bien sûr, si la modification de l'instruction verrouille toujours la majorité des lignes du tableau, la modification n'aura qu'un avantage de lisibilité.
  • Assurez-vous que vous utilisez des séquences plutôt que de verrouiller une table pour en ajouter une à la valeur actuelle la plus élevée.
  • Assurez-vous que vous n'utilisez pas une fonction qui empêche l'utilisation d'un index. Si la fonction est nécessaire, envisagez d'en faire un index basé sur une fonction.
  • Pensez en séries. Déterminez si une boucle exécutant un bloc de PL / SQL faisant des mises à jour peut être réécrite en une seule instruction de mise à jour. Si ce n'est pas le cas, le traitement en bloc pourrait peut-être être utilisé avec BULK COLLECT ... FORALL.
  • Réduisez le travail qui se fait entre le premier UPDATEet le COMMIT. Par exemple, si le code envoie un e-mail après chaque mise à jour, pensez à mettre les e-mails en file d'attente et à les envoyer après avoir validé les mises à jour.
  • Concevez l'application pour gérer l'attente en faisant un SELECT ... FOR UPDATE NOWAITou WAIT 2. Vous pouvez ensuite détecter l'incapacité de verrouiller la ligne et informer l'utilisateur qu'une autre session modifie les mêmes données.

7

Je fournirai une réponse du point de vue du développeur.

À mon avis, lorsque vous rencontrez un conflit de lignes tel que celui que vous décrivez, c'est parce que vous avez un bogue dans votre application. Dans la plupart des cas, ce type de conflit est le signe d'une vulnérabilité de perte de mise à jour. Ce fil sur AskTom explique le concept d'une mise à jour perdue:

Une mise à jour perdue se produit lorsque:

session 1: lire le dossier d'employé de Tom

session 2: lire le dossier d'employé de Tom

session 1: mettre à jour le dossier d'employé de Tom

session 2: mettre à jour le dossier d'employé de Tom

La session 2 annulera les modifications de la session 1 sans jamais les voir, ce qui entraînera une perte de mise à jour.

Vous avez rencontré un effet secondaire désagréable de la mise à jour perdue: la session 2 peut être bloquée car la session 1 n'a pas encore été validée. Le problème principal est cependant que la session 2 met à jour aveuglément l'enregistrement. Supposons que les deux sessions émettent l'instruction:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Après les deux instructions, les modifications de la session1 ont été remplacées, sans que la session2 n'ait été informée que la ligne avait été modifiée par la session 1.


La mise à jour perdue (et l'effet secondaire de contention) ne devrait jamais se produire, ils sont 100% évitables. Vous devez utiliser le verrouillage pour les empêcher avec deux méthodes principales: verrouillage optimiste et pessimiste .

1) Verrouillage pessimiste

Vous souhaitez mettre à jour une ligne. Dans ce mode, vous empêcherez les autres de modifier cette ligne en demandant un verrou sur cette ligne ( SELECT ... FOR UPDATE NOWAITinstruction). Si la ligne est déjà en cours de modification, vous obtiendrez un message d'erreur que vous pourrez traduire gracieusement pour l'utilisateur final (cette ligne est en cours de modification par un autre utilisateur). Si la ligne est disponible, effectuez vos modifications (UPDATE), puis validez chaque fois que votre transaction est terminée.

2) Verrouillage optimiste

Vous souhaitez mettre à jour une ligne. Cependant, vous ne voulez pas maintenir un verrou sur cette ligne, peut-être parce que vous utilisez plusieurs transactions pour mettre à jour la ligne (application sans état basée sur le Web), ou peut-être que vous ne voulez pas qu'un utilisateur garde un verrou trop longtemps ( ce qui peut entraîner le blocage d'autres personnes). Dans ce cas, vous ne demanderez pas immédiatement un verrou. Vous utiliserez un marqueur pour vous assurer que la ligne n'a pas changé lorsque votre mise à jour sera publiée. Vous pouvez mettre en cache la valeur de toutes les colonnes ou utiliser une colonne d'horodatage mise à jour automatiquement ou une colonne basée sur une séquence. Quel que soit votre choix, lorsque vous êtes sur le point d'effectuer votre mise à jour, vous vous assurerez que le marqueur sur cette ligne n'a pas changé en émettant une requête comme:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Si la requête renvoie une ligne, effectuez votre mise à jour. Si ce n'est pas le cas, cela signifie que quelqu'un a modifié la ligne depuis la dernière fois que vous l'avez interrogée. Vous devrez redémarrer le processus depuis le début.

Remarque: Si vous avez une confiance totale sur toutes les applications qui accèdent à votre base de données, vous pouvez compter sur une mise à jour directe pour le verrouillage optimiste. Vous pouvez émettre directement:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Si l'instruction ne met à jour aucune ligne, vous savez que quelqu'un a modifié cette ligne et vous devez tout recommencer.

Si toutes les applications s'accordent sur ce schéma, vous ne serez jamais bloqué par quelqu'un d'autre et vous éviterez la mise à jour aveugle. Cependant, si vous ne verrouillez pas la ligne au préalable, vous êtes toujours susceptible de verrouillage indéfini si une autre application, un travail par lots ou une mise à jour directe n'implémente pas de verrouillage optimiste. C'est pourquoi je conseille de toujours verrouiller la ligne, quel que soit votre choix de schéma de verrouillage (le hit de performance peut être négligeable car vous récupérez toutes les valeurs, y compris le rowid lorsque vous verrouillez la ligne).

TL; DR

  • La mise à jour d'une ligne sans avoir de verrou dessus au préalable expose l'application à un "gel" potentiel. Cela peut être évité si tous les DML de la base de données implémentent un verrouillage optimiste ou pessimiste.
  • Vérifiez que l'instruction SELECT renvoie des valeurs cohérentes avec tout SELECT précédent (pour éviter tout problème de mise à jour perdu)

5

Cette réponse serait probablement admissible à une entrée dans The Daily WTF.

À droite, après avoir retracé les sessions et recherché USER_SOURCE- j'ai trouvé la cause première

  • La cause, sans surprise, était une logique défectueuse
  • Récemment, une déclaration de mise à jour a été ajoutée au SP. L'instruction de mise à jour mettrait essentiellement à jour la table entière. Apparemment, le développeur en question a oublié d'ajouter les clauses where pour mettre à jour les instructions requises.
  • Le tableau mis à jour était, comme mentionné ci-dessus, l'un des tableaux les plus traités et comptait un grand nombre d'enregistrements. La mise à jour prendrait un temps long et angoissant.
  • Le résultat était que les autres sessions n'étaient pas en mesure d'obtenir un verrou sur la table et resteraient dans des contentions de verrouillage de ligne.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.