synchronisation via SELECT FOR UPDATE et données non mises à jour !

Rédigé par gorki - - Aucun commentaire

Le 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é...)

 

Fil RSS des articles de ce mot clé