10
votes

Question de condition de course SQL Server

(Remarque: Ceci est pour MS SQL Server)

Dites que vous avez une table ABC avec une colonne d'identité de clé principale et une colonne de code. Nous voulons chaque rangée ici d'avoir un code unique et généré séquentiellement (basé sur une formule de contrôle de contrôle typique). P>

Dites une autre table def avec une seule ligne, qui stocke le code disponible suivant (Imaginez un aliment simple). P>

Je sais que la logique comme ci-dessous présenterait une condition de course, dans laquelle deux utilisateurs pourraient se retrouver avec le même code: P>

begin tran

declare @x int

select   @x= nextcode FROM  def

waitfor delay '00:00:15'

update def set nextcode = nextcode + 1

select @x

commit tran


1 commentaires

Vous devriez revoir ma réponse tardive: l'accepté n'est pas correct ...


7 Réponses :


0
votes

Vous pouvez définir la colonne sur une valeur calculée qui est persistée. Cela s'occupera de la condition de course.

colonnes calculées < / p>

Note

Utilisation de cette méthode signifie que vous n'avez pas besoin de stocker le code suivant dans une table. La colonne de code devient le point de référence.

Mise en œuvre

Donnez la colonne les propriétés suivantes sous la spécification de colonne calculée.

formule = dbo.getNextCode ()

est persisté = oui xxx


5 commentaires

Dans le corps de la fonction Comment accéder à la dernière valeur (c'est-à-dire persisté)?


Cela dépend vraiment de la conception de la table, mais l'exemple mis à jour que je donne devrait couvrir la plupart des cas.


Désolé - aurait dû poser deux questions dans le premier commentaire! Comment le code suivant est-il persisté, ou où peut-être?


Découvrez l'URL que j'ai ajouté à ma réponse.


Cela résoudra-t-il le problème de la condition de course? Et si deux clients l'appellent en même temps? (ou lire dans la colonne calculée en même temps)



2
votes

Recap:

  • Vous avez commencé une transaction. Cela ne fait pas « faire » quelque chose en soi, il modifie le comportement ultérieur
  • Vous lire les données à partir d'une table. Le niveau d'isolation par défaut est READ COMMITTED, cette instruction select est pas fait partie de la transaction.
  • Vous puis attendez 15 secondes
  • Vous émettez alors une mise à jour. Avec la transaction déclarée, cela va générer un verrou jusqu'à ce que la transaction est validée.
  • Vous vous engagez alors la transaction, libérant le verrou.

    Alors, vous deviner couru cette fois dans deux fenêtres (A et B):

    • A lire la valeur de la table "suivant" def, puis est passé en mode d'attente
    • B lire la même valeur « à côté » de la table, puis est passé en mode d'attente. (Comme A ne fait une lecture, la transaction n'a rien serrure.)
    • A ensuite mis à jour la table, et probablement commited le changement avant B quitté l'état d'attente.
    • B puis mis à jour la table, après l'écriture de A a été commise.

      Essayez de mettre la déclaration d'attente après la mise à jour, avant que la livraison, et voir ce qui se passe.


0 commentaires

0
votes

Il s'agit en fait d'un problème courant dans les bases de données SQL et c'est pourquoi la plupart (tous?) d'entre eux ont des caractéristiques intégrées pour s'occuper de cette question d'obtenir un identifiant unique. Voici quelques éléments à examiner si vous utilisez MySQL ou Postgres. Si vous utilisez une base de données différente, je parie que le fournit quelque chose de très similaire.

Un bon exemple de ceci est des séquences Postgres que vous pouvez consulter ici:

Séquences Postgres

mySQL utilise quelque chose appelé incréments automatiques.

incrément automatique MySQL


0 commentaires

1
votes

Ce n'est pas une vraie condition de race. C'est plus un problème courant avec les transactions simultanées. Une solution consiste à définir une serrure de lecture sur la table et à une série de sérialisation en place.


0 commentaires

9
votes

Définissez le niveau d'isolation de la transaction sur Serializable.
À des niveaux d'isolation inférieurs, d'autres transactions peuvent lire les données d'une rangée lue (mais pas encore modifiée) dans cette transaction. Donc, deux transactions peuvent en effet lire la même valeur. À très faible isolement (lire non engagés), d'autres transactions peuvent même lire des données après avoir été modifiées (mais avant de s'engager) ...

Révision des détails sur les niveaux d'isolation SQL Server ici p>

SO suite est que le niveau d'isolement est une pièce crtitique ici pour contrôler quel niveau d'accès à d'autres transactions d'accès entrer dans celui-ci. p>

note. De lien , sur sérialisable em> Les déclarations ne peuvent pas lire les données modifiées mais non encore engagées par d'autres transactions em>.
En effet, les verrous sont placés lorsque la ligne est modifiée, non lorsque le Trans code> se produit, de sorte que ce que vous avez fait peut toujours permettre à une autre transaction de lire l'ancienne valeur jusqu'au point où vous le modifiez. Donc, je modifierais la logique pour la modifier dans la même instruction que vous le lisez, mettez ainsi le verrouillage à la fois. P>

begin tran
declare @x int
update def set @x= nextcode, nextcode += 1
waitfor delay '00:00:15'
select @x
commit tran


1 commentaires

Je pense que vous voulez dire mise à jour def Set @ x = NextCode, NextCode + = 1



5
votes

Au fur et à mesure que d'autres intervenants ont mentionné, vous pouvez définir le niveau d'isolation de la transaction pour vous assurer que tout ce que vous avez "lu" à l'aide d'une instruction SELECT ne peut pas changer dans une transaction.

Vous pouvez également supprimer une serrure spécifiquement sur la table def. En ajoutant la syntaxe avec blocklock code> après le nom de la table, par exemple P>

SELECT nextcode FROM DEF WITH HOLDLOCK


0 commentaires

5
votes

réponse tardive. Vous voulez éviter une condition de course ...

"Condition de course de la file d'attente du processus SQL Server" < / p>


0 commentaires