synchronisation via SELECT FOR UPDATE et données non mises à jour !
Rédigé par gorki Aucun commentaireLe problème :
Un problème tout simple ou presque :)
Deux threads qui discutent avec la base de données, synchronisés par un "SELECT ... FOR UPDATE" sur le même objet.
Le deuxième thread n'a pas toujours le résultat du travail du premier thread !
Solution :
Alors j'écarte toutes les hypothèses qui m'ont été faites :
- une seule transaction par thread de la prise de lock au commit
- transaction en REQUIRED_NEW
- le lock via le SELECT FOR UPDATE est la première instruction des threads
- pas d'instruction DDL pendant les threads
- le SELECT FOR UPDATE fonctionne correctement
La réponse : Mysql utilise par défaut le mode d'isolation READ REPEATABLE alors que la plupart des autres bases utilisent READ COMMITED.
Comme j'imagine que ce n'est pas évident, quelques explications :
Thread n°1 | Thread n°2 | Mysql Mode : REPEATABLE READ | Mysql Mode : READ COMMITED |
SELECT tableLock FOR UPDATE WHERE id = 1 | |||
UPDATE user.status = 4 | |||
start thread n°2 | |||
SELECT tableLock FOR UPDATE WHERE id = 1 | Snapshot de la base pour avoir des données constantes pendant la transaction | Pas de snapshot, ce sont les dernières données en base | |
Commit | |||
Obtention du lock APRES le commit | |||
select user.status | status est null / non mis à jour | status = 4 ! |
Remarques :
- Le thread n°2 déclenché par le thread n°1 se met en attente du lock. En faisant un SELECT !. C'est là le problème. Le mode REPEATABLE READ fait un snapshot valable sur la base entière au premier select.
- ce point (snapshot sur la base entière) n'est pas très explicite dans les documentations
Solutions :
- passer MYSQL en mode READ COMMITED (un peu performant en plus...)
- mettre les mises à jour des données autres dans des sous-transactions
- envisager un autre mode de synchronisation hors transaction (cache distribué...)