MySQL: transactions vs tables de verrouillage

110

Je suis un peu confus avec les transactions par rapport aux tables de verrouillage pour assurer l'intégrité de la base de données et m'assurer qu'un SELECT et une MISE À JOUR restent synchronisés et qu'aucune autre connexion ne l'interfère. J'ai besoin de:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Je dois m'assurer qu'aucune autre requête n'interférera et n'effectuera la même chose SELECT(en lisant «l'ancienne valeur» avant que la connexion ne termine la mise à jour de la ligne.

Je sais que je peux par défaut LOCK TABLES tablem'assurer qu'une seule connexion le fait à la fois, et la déverrouiller lorsque j'ai terminé, mais cela semble excessif. L'encapsulation de cela dans une transaction ferait-elle la même chose (en s'assurant qu'aucune autre connexion ne tente le même processus pendant qu'une autre est en cours de traitement)? Ou serait un SELECT ... FOR UPDATEou SELECT ... LOCK IN SHARE MODEserait mieux?

Ryan
la source

Réponses:

173

Le verrouillage des tables empêche les autres utilisateurs de base de données d'affecter les lignes / tables que vous avez verrouillées. Mais les verrous, en eux-mêmes, ne garantiront PAS que votre logique sortira dans un état cohérent.

Pensez à un système bancaire. Lorsque vous payez une facture en ligne, il y a au moins deux comptes concernés par la transaction: Votre compte, sur lequel l'argent est prélevé. Et le compte du séquestre, dans lequel l'argent est transféré. Et le compte de la banque, sur lequel ils déposeront avec plaisir tous les frais de service facturés lors de la transaction. Étant donné (comme tout le monde le sait de nos jours) que les banques sont extraordinairement stupides, disons que leur système fonctionne comme ceci:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Désormais, sans verrous ni transactions, ce système est vulnérable à diverses conditions de concurrence, dont la plus importante est plusieurs paiements effectués sur votre compte ou sur le compte du destinataire en parallèle. Bien que votre code ait récupéré votre solde et effectue les énormes_overdraft_fees () et ainsi de suite, il est tout à fait possible qu'un autre paiement exécute le même type de code en parallèle. Ils récupéreront votre solde (disons 100 $), effectueront leurs transactions (retirez les 20 $ que vous payez et les 30 $ qu'ils vous foutent), et maintenant les deux chemins de code ont deux soldes différents: 80 $ et 70 $. En fonction de celui qui se termine en dernier, vous vous retrouverez avec l'un de ces deux soldes dans votre compte, au lieu des 50 $ avec lesquels vous auriez dû vous retrouver (100 $ - 20 $ - 30 $). Dans ce cas, "erreur bancaire en votre faveur"

Maintenant, disons que vous utilisez des verrous. Le paiement de votre facture (20 $) arrive en premier, il gagne et verrouille l'enregistrement de votre compte. Maintenant, vous avez une utilisation exclusive et pouvez déduire les 20 $ du solde, et réécrire le nouveau solde en paix ... et votre compte se termine avec 80 $ comme prévu. Mais ... euh ... vous essayez d'aller mettre à jour le compte du destinataire, et il est verrouillé, et verrouillé plus longtemps que le code ne le permet, expirant votre transaction ... Nous avons affaire à des banques stupides, donc au lieu d'avoir une erreur appropriée manipulation, le code tire juste un exit(), et vos 20 $ disparaissent dans une bouffée d'électrons. Maintenant, vous êtes à 20 $ et vous devez toujours 20 $ au destinataire, et votre téléphone est repris.

Alors ... saisissez des transactions. Vous démarrez une transaction, vous débitez votre compte de 20 $, vous essayez de créditer le destinataire de 20 $ ... et quelque chose explose à nouveau. Mais cette fois, au lieu de cela exit(), le code peut tout simplement faire rollback, et pouf, vos 20 $ sont ajoutés par magie à votre compte.

En fin de compte, cela se résume à ceci:

Les verrous empêchent quiconque d'interférer avec les enregistrements de la base de données que vous traitez. Les transactions empêchent les erreurs "ultérieures" d'interférer avec les choses "antérieures" que vous avez faites. Ni l'un ni l'autre ne peuvent garantir que les choses se passent bien à la fin. Mais ensemble, ils le font.

dans la leçon de demain: The Joy of Deadlocks.

Marc B
la source
4
Je suis aussi / toujours confus. Supposons que le compte du destinataire contenait 100 $ pour démarrer et que nous ajoutions le paiement de la facture de 20 $ à partir de notre compte. Ma compréhension des transactions est que lorsqu'elles commencent, toute opération en cours de transaction voit la base de données dans l'état où elle se trouvait au début de la transaction. c'est-à-dire: jusqu'à ce que nous le changions, le compte du destinataire a 100 $. Donc ... lorsque nous ajoutons 20 $, nous établissons en fait un solde de 120 $. Mais que se passe-t-il si, lors de notre transaction, quelqu'un a vidé le compte du destinataire à 0 $? Cela est-il empêché d'une manière ou d'une autre? Obtiennent-ils à nouveau 120 $ par magie? Est-ce la raison pour laquelle des verrous sont également nécessaires?
Russ
Oui, c'est là que les verrous entrent en jeu. Un système approprié verrouillerait l'enregistrement en écriture afin que personne d'autre ne puisse mettre à jour l'enregistrement pendant que la transaction progresse. Un système paranoïaque mettrait un verrou inconditionnel sur le dossier afin que personne ne puisse lire non plus la balance «périmée».
Marc B
1
En gros, regardez les transactions comme sécurisant les choses à l'intérieur de votre chemin de code. Verrouille les éléments sécurisés sur des chemins de code «parallèles». Jusqu'à ce que les impasses frappent ...
Marc B
1
@MarcB, alors pourquoi devons-nous faire le verrouillage explicitement si l'utilisation des transactions seules garantit déjà que les verrous sont en place? Y aura-t-il même un cas où nous devrons faire un verrouillage explicite parce que les transactions seules sont insuffisantes?
Pacerier
2
Cette réponse n'est pas correcte et peut conduire à de fausses conclusions. Cette déclaration: "Les verrous empêchent quiconque d'interférer avec les enregistrements de la base de données avec lesquels vous traitez. Les transactions empêchent les erreurs" ultérieures "d'interférer avec les actions" antérieures "que vous avez effectuées. fin. Mais ensemble, ils le font. " - vous ferait virer, c'est extrêmement faux et stupide Voir les articles: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems) et dev.mysql.com/doc/refman/5.1/ fr /…
Nikola Svitlica
14

Vous voulez une transaction SELECT ... FOR UPDATEou à l' SELECT ... LOCK IN SHARE MODEintérieur d'une transaction, comme vous l'avez dit, car normalement les SELECT, qu'ils soient dans une transaction ou non, ne verrouillent pas une table. Celui que vous choisirez dépendra du fait que vous souhaitez que d'autres transactions puissent lire cette ligne pendant que votre transaction est en cours.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTne fera pas l'affaire pour vous, car d'autres transactions peuvent encore arriver et modifier cette ligne. Ceci est mentionné en haut du lien ci-dessous.

Si d'autres sessions mettent à jour simultanément la même table, [...] vous pouvez voir la table dans un état qui n'a jamais existé dans la base de données.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

Alison R.
la source
7

Les concepts de transaction et les verrous sont différents. Cependant, la transaction utilisait des verrous pour l'aider à suivre les principes ACID. Si vous voulez que la table empêche les autres de lire / écrire au même moment pendant que vous êtes en lecture / écriture, vous avez besoin d'un verrou pour ce faire. Si vous voulez vous assurer de l'intégrité et de la cohérence des données, il vaut mieux utiliser les transactions. Je pense que des concepts mixtes de niveaux d'isolement dans les transactions avec des verrous. Veuillez rechercher les niveaux d'isolement des transactions, SERIALIZE doit être le niveau souhaité.

tczhaodachuan
la source
Cela devrait être la bonne réponse. Le verrouillage sert à éviter les conditions de concurrence et les transactions permettent de mettre à jour plusieurs tables avec des données dépendantes. Deux concepts totalement différents, malgré que les transactions utilisent des verrous.
Blue Water
6

J'ai eu un problème similaire lors de la tentative de a IF NOT EXISTS ..., puis de l'exécution d'une INSERTcondition de concurrence lorsque plusieurs threads mettaient à jour la même table.

J'ai trouvé la solution au problème ici: Comment écrire des requêtes INSERT IF NOT EXISTS en SQL standard

Je me rends compte que cela ne répond pas directement à votre question, mais le même principe de vérification et d'insertion en une seule déclaration est très utile; vous devriez pouvoir le modifier pour effectuer votre mise à jour.

Tony
la source
2

Vous êtes confondu avec le verrouillage et la transaction. Ce sont deux choses différentes dans RMDB. Le verrouillage empêche les opérations simultanées pendant que la transaction se concentre sur l'isolation des données. Consultez cet excellent article pour la clarification et une solution élégante.

David
la source
1
Les verrous empêchent les autres d'interférer avec les enregistrements sur lesquels vous travaillez décrit succinctement ce qu'il fait, et les transactions empêchent les erreurs ultérieures (celles d'autres personnes effectuant des modifications en parallèle) d'interférer avec les choses précédentes que vous avez faites (en autorisant la restauration si quelqu'un a fait quelque chose en parallèle) résume assez bien les transactions ... qu'est-ce qui est confus dans sa compréhension de ces sujets?
steviesama
1

J'utiliserais un

START TRANSACTION WITH CONSISTENT SNAPSHOT;

pour commencer, et un

COMMIT;

pour finir avec.

Tout ce que vous faites entre les deux est isolé des autres utilisateurs de votre base de données si votre moteur de stockage prend en charge les transactions (qui est InnoDB).

Martin Schapendonk
la source
1
Sauf que la table dans laquelle il sélectionne ne sera pas verrouillée sur d'autres sessions à moins qu'il ne la verrouille spécifiquement (ou jusqu'à ce que sa mise à jour se produise), ce qui signifie que d'autres sessions pourraient venir et la modifier entre le SELECT et la mise à jour.
Alison R.
Après avoir lu sur START TRANSACTION WITH CONSISTENT SNAPSHOT dans la documentation MySQL, je ne vois pas où cela empêche réellement une autre connexion de mettre à jour la même ligne. Je crois comprendre que cela verrait cependant la table commencer au début de la transaction. Ainsi, si une autre transaction est en cours, a déjà obtenu une ligne et est sur le point de la mettre à jour, la 2ème transaction verra toujours la ligne avant qu'elle ne soit mise à jour. Il pourrait donc essayer de mettre à jour la même ligne que l'autre transaction est sur le point de faire. Est-ce correct ou est-ce que je manque quelque chose dans la progression?
Ryan
1
@Ryan Il ne fait aucun verrouillage; vous avez raison. Le verrouillage (ou non) est déterminé par le type d'opérations que vous effectuez (SELECT / UPDATE / DELETE).
Alison R.
4
Je vois. Cela donne votre propre cohérence de lecture de transaction, mais n'empêche pas les autres utilisateurs de modifier une ligne juste avant vous.
Martin Schapendonk