web-dev-qa-db-fra.com

Comment implémenter correctement le verrouillage optimiste dans MySQL

Comment implémenter correctement le verrouillage optimiste dans MySQL?

Notre équipe a déduit que nous devons faire # 4 ci-dessous, sinon il y a un risque qu'un autre thread puisse mettre à jour la même version de l'enregistrement, mais nous aimerions valider que c'est la meilleure façon de le faire.

  1. Créez un champ de version sur la table pour laquelle vous souhaitez utiliser un verrouillage optimiste, par exemple nom de colonne = "version"
  2. Sur les sélections, assurez-vous d'inclure la colonne de version et notez la version
  3. Lors d'une mise à jour ultérieure de l'enregistrement, l'instruction de mise à jour doit émettre "où version = X" où X est la version que nous avons reçue dans # 2 et définir le champ de version pendant cette instruction de mise à jour sur X + 1.
  4. Effectuez un SELECT FOR UPDATE sur l'enregistrement que nous allons mettre à jour afin de sérialiser qui peut apporter des modifications à l'enregistrement que nous essayons de mettre à jour.

Pour clarifier, nous essayons d'empêcher deux threads qui sélectionnent le même enregistrement dans la même fenêtre de temps où ils récupèrent la même version de l'enregistrement de se remplacer les uns les autres s'ils devaient essayer de mettre à jour l'enregistrement en même temps. Nous pensons que si nous ne faisons pas # 4, il y a une chance que si les deux threads entrent leurs transactions respectives en même temps (mais n'ont pas encore publié leurs mises à jour), lorsqu'ils vont mettre à jour, le deuxième thread qui utilisera la MISE À JOUR ... où version = X fonctionnera sur les anciennes données.

Avons-nous raison de penser que nous devons effectuer ce verrouillage pessimiste lors de la mise à jour, même si nous utilisons des champs de version/verrouillage optimiste?

13
BestPractices

Votre développeur se trompe. Vous avez besoin de soit SELECT ... FOR UPDATE ou versionnage des lignes, pas les deux.

Essayez-le et voyez. Ouvrez trois sessions MySQL (A), (B) et (C) vers la même base de données.

Dans (C) problème:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

À la fois (A) et (B) émettez un UPDATE qui teste et définit la version de la ligne, en changeant le texte winner dans chacun afin que vous puissiez voir quelle session est laquelle:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

Maintenant en (C), UNLOCK TABLES; pour libérer le verrou.

(A) et (B) courra pour le verrouillage de ligne. L'un d'eux gagnera et obtiendra le cadenas. L'autre bloquera la serrure. Le gagnant qui a obtenu le verrou procédera au changement de ligne. En supposant (A) est le gagnant, vous pouvez maintenant voir la ligne modifiée (toujours non validée donc non visible pour les autres transactions) avec un SELECT * FROM test WHERE id = 1.

Maintenant COMMIT dans la session gagnante, dites (A).

(B) obtiendra le verrou et procédera à la mise à jour. Cependant, la version ne correspond plus, elle ne modifiera donc aucune ligne, comme indiqué par le résultat du nombre de lignes. Un seul UPDATE a eu un effet, et l'application cliente peut clairement voir quels UPDATE ont réussi et lesquels ont échoué. Aucun verrouillage supplémentaire n'est nécessaire.

Voir les journaux de session sur Pastebin ici . J'ai utilisé mysql --Prompt="A> " etc pour faire facilement la différence entre les sessions. J'ai copié et collé la sortie entrelacée dans une séquence temporelle, donc ce n'est pas une sortie totalement brute et il est possible que j'aurais pu faire des erreurs en la copiant et en la collant. Testez-le vous-même pour voir.


Si vous n'aviez pas ajouté un champ de version de ligne, alors vous auriez besoin à SELECT ... FOR UPDATE pour pouvoir assurer de manière fiable la commande.

Si vous y réfléchissez, un SELECT ... FOR UPDATE est complètement redondant si vous faites immédiatement un UPDATE sans réutiliser les données du SELECT, ou si vous utilisez le versionnage en ligne. UPDATE prendra quand même un verrou. Si quelqu'un d'autre met à jour la ligne entre votre lecture et votre écriture suivante, votre version ne correspondra plus, donc votre mise à jour échouera. Voilà comment fonctionne le verrouillage optimiste.

Le but de SELECT ... FOR UPDATE est:

  • Gérer l'ordre des verrous pour éviter les blocages; et
  • Pour étendre la portée d'un verrou de ligne lorsque vous souhaitez lire des données à partir d'une ligne, modifiez-la dans l'application et écrivez une nouvelle ligne basée sur l'original sans avoir à utiliser l'isolation SERIALIZABLE ou le versionnage de ligne .

Vous n'avez pas besoin d'utiliser à la fois le verrouillage optimiste (version de ligne) et SELECT ... FOR UPDATE. Utilise l'un ou l'autre.

17
Craig Ringer
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

Aucun verrou (pas de table, pas de transaction) nécessaire ou même souhaité:

  • UPDATE est atomique
  • LAST_INSERT_ID () est spécifique à la session, donc thread-safe.
0
Rick James