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.
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?
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:
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.
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é: