Gestion de l'accès simultané lors de l'utilisation du modèle SELECT-UPDATE


25

Disons que vous avez le code suivant (veuillez ignorer que c'est affreux):

BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else

À mon avis, cela ne gère PAS correctement la concurrence. Ce n'est pas parce que vous avez une transaction que quelqu'un d'autre ne lira pas la même valeur que vous aviez avant d'arriver à votre déclaration de mise à jour.

Maintenant, en laissant le code tel quel (je me rends compte que cela est mieux géré comme une seule instruction ou encore mieux en utilisant une colonne d'auto-incrémentation / d'identité), quels sont les moyens sûrs de le faire gérer correctement la concurrence et d'empêcher les conditions de concurrence qui permettent à deux clients d'obtenir la même chose valeur id?

Je suis presque sûr que l'ajout d'un WITH (UPDLOCK, HOLDLOCK)au SELECT fera l'affaire. Le niveau d'isolement des transactions SERIALIZABLE semble également fonctionner car il interdit à quiconque de lire ce que vous avez fait jusqu'à la fin du transfert ( MISE À JOUR : c'est faux. Voir la réponse de Martin). Est-ce vrai? Vont-ils tous les deux fonctionner aussi bien? Est-ce que l'un est préféré à l'autre?

Imaginez faire quelque chose de plus légitime qu'une mise à jour d'ID - un calcul basé sur une lecture que vous devez mettre à jour. Il pourrait y avoir de nombreuses tables impliquées, dont certaines vous écrirez et d'autres que vous ne ferez pas. Quelle est la meilleure pratique ici?

Après avoir écrit cette question, je pense que les conseils de verrouillage sont meilleurs car vous ne verrouillez que les tables dont vous avez besoin, mais j'apprécierais la contribution de n'importe qui.

PS Et non, je ne connais pas la meilleure réponse et je veux vraiment avoir une meilleure compréhension! :)


Juste pour clarification: voulez-vous empêcher 2 clients de lire la même valeur ou d'émettre updatequi peuvent être basés sur des données obsolètes? Dans ce dernier cas, vous pouvez utiliser la rowversioncolonne pour vérifier si la ligne à mettre à jour n'a pas été modifiée depuis sa lecture.
a1ex07

Nous ne voulons pas qu'un deuxième client obtienne l'ancienne valeur d'ID avant qu'elle ne soit mise à jour vers la nouvelle valeur par le premier client. Il devrait bloquer.
ErikE

Réponses:


11

Il s'agit simplement de l' SERIALIZABLEaspect du niveau d'isolement. Oui, cela fonctionnera, mais avec un risque de blocage.

Deux transactions pourront toutes deux lire la ligne simultanément. Ils ne se bloqueront pas car ils prendront un Sverrou d' objet ou des RangeS-Sverrous d' index en fonction de la structure de la table et ces verrous sont compatibles . Mais ils se bloqueront lorsqu'ils tenteront d'acquérir les verrous nécessaires à la mise à jour ( IXverrou d' objet ou index RangeS-Urespectivement), ce qui entraînera un blocage.

À la place, l'utilisation d'un UPDLOCKindice explicite sérialisera les lectures, évitant ainsi le risque de blocage.


+1 mais: pour les tables de tas, vous pouvez toujours obtenir un blocage de conversion même avec des verrous de mise à jour: sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/11/…
AK

Bizarre, @alex. J'imagine que cela a à voir avec une condition de course du moteur essayant de trouver quoi verrouiller avant de réellement le verrouiller ...
ErikE

@ErikE - Le blocage de conversion dans l'article d'Alex se convertit de IXà Xsur le tas lui-même. Fait intéressant, aucune ligne ne se qualifie donc aucun verrou de ligne n'est retiré. Je ne sais pas pourquoi il prend le Xverrou du tout.
Martin Smith

11

Je pense que la meilleure approche pour vous serait d'exposer votre module à une concurrence élevée et de voir par vous-même. Parfois, UPDLOCK seul suffit et il n'est pas nécessaire de HOLDLOCK. Parfois, sp_getapplock fonctionne très bien. Je ne ferais aucune déclaration générale ici - parfois, l'ajout d'un index, d'un déclencheur ou d'une vue indexée supplémentaire modifie le résultat. Nous devons souligner le code de test et voir par nous-mêmes au cas par cas.

Je l' ai écrit plusieurs exemples de tests de stress ici

Edit: pour une meilleure connaissance des internes, vous pouvez lire les livres de Kalen Delaney. Cependant, les livres peuvent se désynchroniser comme toute autre documentation. En outre, il y a trop de combinaisons à considérer: six niveaux d'isolement, de nombreux types de verrous, des index cluster / non cluster et qui sait quoi d'autre. C'est beaucoup de combinaisons. En plus de cela, SQL Server est une source fermée, nous ne pouvons donc pas télécharger le code source, le déboguer, etc. - ce serait la source ultime de connaissances. Tout le reste peut être incomplet ou obsolète après la prochaine version ou le prochain Service Pack.

Donc, vous ne devriez pas décider de ce qui fonctionne pour votre système sans vos propres tests de résistance. Quoi que vous ayez lu, cela peut vous aider à comprendre ce qui se passe, mais vous devez prouver que les conseils que vous avez lus vous conviennent. Je pense que personne ne peut le faire pour vous.


9

Dans ce cas particulier, l'ajout d'une UPDLOCKserrure à la SELECTborne permettrait en effet d'éviter les anomalies. L'ajout de HOLDLOCKn'est pas nécessaire car un verrou de mise à jour est maintenu pendant la durée de la transaction, mais j'avoue l'inclure moi-même comme une habitude (peut-être mauvaise) dans le passé.

Imaginez faire quelque chose de plus légitime qu'une mise à jour d'ID, un calcul basé sur une lecture que vous devez mettre à jour. Il pourrait y avoir de nombreuses tables impliquées, dont certaines vous écrirez et d'autres que vous ne ferez pas. Quelle est la meilleure pratique ici?

Il n'y a pas de meilleure pratique. Votre choix de contrôle d'accès simultané doit être basé sur les exigences de l'application. Certaines applications / transactions doivent s'exécuter comme si elles détenaient la propriété exclusive de la base de données, évitant à tout prix les anomalies et les inexactitudes. D'autres applications / transactions peuvent tolérer un certain degré d'interférence les unes des autres.

  • Récupération d'un niveau de stock en bandes (<5, 10+, 50+, 100+) pour un produit dans une boutique en ligne = lecture sale (inexact n'a pas d'importance).
  • Vérifier et réduire le niveau de stock sur cette caisse de la boutique en ligne = lecture répétable (nous devons avoir le stock avant de vendre, nous ne devons PAS nous retrouver avec un niveau de stock négatif).
  • Déplacer de l'argent entre mon compte courant et mon compte d'épargne à la banque = sérialisable (ne pas mal calculer ou égarer mon argent!).

Edit: @ Le commentaire d'AlexKuznetsov m'a incité à relire la question et à supprimer l'erreur très évidente dans ma réponse. Remarque à vous-même sur l'affichage tard dans la nuit.

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.