web-dev-qa-db-fra.com

Verrouillage optimiste dans MySQL

Je ne trouve aucun détail sur le verrouillage optimiste dans MySQL. J'ai lu que le démarrage d'une transaction permet de garder les mises à jour sur deux entités synchronisées - cela n'empêche pas deux utilisateurs de mettre à jour les données en même temps, provoquant un conflit.

Un verrouillage apparemment optimiste résoudra ce problème? Comment cela est-il appliqué dans MySQL? Existe-t-il une syntaxe/mot clé SQL pour cela? Ou est-ce que MySQL a un comportement par défaut?

Merci les gars.

35
Green Acorn

Le fait est que le verrouillage optimiste n'est pas une fonctionnalité de base de données, ni pour MySQL ni pour d'autres: le verrouillage optimiste est une pratique qui est appliquée à l'aide de la base de données avec des instructions standard.

Prenons un exemple très simple et disons que vous voulez le faire dans un code que plusieurs utilisateurs/clients peuvent exécuter simultanément:

  1. SÉLECTIONNEZ les données d'une ligne ayant un champ ID (iD) et deux champs de données (val1, val2)
  2. faire éventuellement vos calculs avec des données
  3. MISE À JOUR des données de cette ligne

La méthode NO LOCKING est:

REMARQUE: tout le code {entre accolades} est destiné à être dans le code de l'application et non (nécessairement) dans le côté SQL

- SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId;
 - {go on with your other code}

La méthode de VERROUILLAGE OPTIMISTE est:

- SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId
           AND val1 = @oldVal1
           AND val2 = @oldVal2;
 - {if AffectedRows == 1 }
 -     {go on with your other code}
 - {else}
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

Notez que le point clé se trouve dans la structure de l'instruction UPDATE et le nombre de vérifications de lignes affectées ultérieures. Ce sont ces deux choses ensemble qui permettent à votre code de réaliser que quelqu'un a déjà modifié les données entre les deux lorsque vous avez exécuté SELECT et UPDATE. Notez que tout a été fait sans transaction! Cela n'a été possible (absence de transactions) que parce qu'il s'agit d'un exemple très simple, mais cela indique également que le point clé du verrouillage optimiste n'est pas dans les transactions elles-mêmes.

Et les TRANSACTIONS alors?

 - SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - BEGIN TRANSACTION;
 - UPDATE anotherTable
       SET col1 = @newCol1,
           col2 = @newCol2
       WHERE iD = @theId;
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId
           AND val1 = @oldVal1
           AND val2 = @oldVal2;
 - {if AffectedRows == 1 }
 -     COMMIT TRANSACTION;
 -     {go on with your other code}
 - {else}
 -     ROLLBACK TRANSACTION;
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

Ce dernier exemple montre que si vous vérifiez les collisions à un moment donné et découvrez qu'une collision s'est produite alors que vous avez déjà modifié d'autres tables/lignes .. ..puis avec les transactions, vous pouvez annuler TOUTES les modifications que vous avez effectuées depuis le début. De toute évidence, c'est à vous (qui savez ce que fait votre application) de décider quelle est la quantité d'opérations à annuler pour chaque collision possible et, en fonction de cela, de décider où placer les limites des transactions et où vérifier les collisions avec le spécial UPDATE + AffectedRows vérifier.

Dans ce cas avec les transactions, nous avons séparé le moment où nous effectuons la MISE À JOUR du moment où elle est validée. Que se passe-t-il donc lorsqu'un "autre processus" effectue une mise à jour dans ce laps de temps? Pour savoir ce qui se passe exactement, il faut fouiller dans les détails du niveau d'isolement (et comment est géré sur chaque moteur). Par exemple, dans le cas de Microsoft SQL Server avec READ_COMMITTED, les lignes mises à jour sont verrouillées jusqu'à ce que le COMMIT donc "autre processus" ne puisse rien faire (reste en attente) sur ces lignes, ni un SELECT (en fait, il ne peut que READ_COMMITTED) . Ainsi, puisque l'activité "autre processus" est différée, la MISE À JOUR échouera.

L'option VERSIONING OPTIMISTIC LOCKING:

 - SELECT iD, val1, val2, version
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2,
           version = version + 1
       WHERE iD = @theId
           AND version = @oldversion;
 - {if AffectedRows == 1 }
 -     {go on with your other code}
 - {else}
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

Ici, nous montrons qu'au lieu de vérifier si la valeur est toujours la même pour tous les champs, nous pouvons utiliser un champ dédié (qui est modifié chaque fois que nous faisons une MISE À JOUR) pour voir si quelqu'un était plus rapide que nous et a changé la ligne entre nos SELECT et UPDATE. Ici, l'absence de transactions est due à la simplicité comme dans le premier exemple et n'est pas liée à l'utilisation de la colonne de version. Encore une fois, cette utilisation de la colonne dépend de l'implémentation dans le code d'application et non d'une fonctionnalité de moteur de base de données.

Plus que cela, il y a d'autres points qui, je pense, rendraient cette réponse trop longue (est déjà trop longue), je ne les mentionne que maintenant avec quelques références:

  • niveau d'isolement des transactions ( ici pour MySQL ) sur l'effet des transactions sur les SELECT.
  • pour l'INSERT sur les tables avec des clés primaires non générées automatiquement (ou des contraintes uniques), il échouera automatiquement sans avoir besoin de vérification particulière si deux processus tentent d'insérer les mêmes valeurs là où elles doivent être uniques.
  • si vous n'avez pas de colonne id (clé primaire ou contraintes uniques), un seul SELECT + UPDATE nécessite des transactions car vous pourriez avoir la surprise qu'après les modifications apportées par d'autres, il y a plus de lignes que prévu correspondant aux critères de la clause WHERE de UPDATE.

Comment vérifier dans la pratique et devenir confiant

Étant donné que la valeur du niveau d'isolement et l'implémentation peuvent être différentes, le meilleur conseil (comme d'habitude sur ce site) est d'effectuer un test sur la plate-forme/l'environnement utilisé.

Cela peut sembler difficile, mais en réalité, cela peut être fait assez facilement à partir de n'importe quel environnement de développement de base de données en utilisant deux fenêtres distinctes et en commençant sur chacune une transaction puis en exécutant les commandes une par une.

À un moment donné, vous verrez que l'exécution de la commande se poursuit indéfiniment. Ensuite, lorsque sur l'autre fenêtre, il est appelé COMMIT ou ROLLBACK, il termine l'exécution.

Voici quelques commandes très basiques prêtes à être testées comme cela vient d'être décrit.

Utilisez-les pour créer le tableau et une ligne utile:

CREATE TABLE theTable(
    iD int NOT NULL,
    val1 int NOT NULL,
    val2 int NOT NULL
)
INSERT INTO theTable (iD, val1, val2) VALUES (1, 2 ,3);

Puis ce qui suit sur deux fenêtres différentes et étape par étape:

BEGIN TRAN

SELECT val1, val2 FROM theTable WHERE iD = 1;

UPDATE theTable
  SET val1=11
  WHERE iD = 1 AND val1 = 2 AND val2 = 3;

COMMIT TRAN

puis changez l'ordre des commandes et l'ordre d'exécution dans l'ordre que vous pensez.

118
Diego Mazzaro