Comment interroger et augmenter une valeur (compteur) de manière thread-safe? (éviter les conditions de course)

10

Dans un tableau où chaque ligne a un compteur (juste une valeur entière), j'ai besoin d'obtenir la valeur actuelle et de l'augmenter en même temps .

Effectivement, je veux faire ceci:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Mais faire cela comme deux requêtes n'est évidemment pas thread-safe: plusieurs processus faisant la même chose (sur la même ligne) peuvent obtenir la même valeur de compteur. Je leur ai besoin de tout être unique, de sorte que chaque processus obtiendrait la réelle valeur actuelle et l' augmenter par un.

Je peux penser à une construction où j'implémente un verrou manuel par ligne, mais je me demande s'il y a un moyen plus simple de le faire?

RocketNuts
la source
utiliser des transactions peut-être?
ypercubeᵀᴹ

Réponses:

15

Les déclarations de mise à jour fonctionnent parfaitement bien sans le sélectionner avant! Étant donné que les instructions simples sont sûres par définition, même deux requêtes UPDATE effectuées en même temps uniquement entraîneront une incrémentation de la ligne deux fois.

Si vous voulez réellement sélectionner la valeur de votre script PHP, faire quelque chose avec lui et que vous souhaitez mettre à jour cette valeur de compteur exacte, vous pouvez faire ce qui suit:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

Cela démarre une nouvelle transaction, puis sélectionne les lignes que vous souhaitez mettre à jour et les verrouille exclusivement. Vous pouvez ensuite mettre à jour ceux-ci en toute sécurité sans vous soucier des autres clients qui modifient leur contenu ou même qui accèdent aux lignes verrouillées. Enfin, vous devez valider vos modifications.

Vous devriez également lire quelque chose sur les niveaux d'isolement . Vous ne voulez probablement pas une valeur comme READ UNCOMMITTEDniveau d'isolement. Tout le reste devrait convenir à ce cas d'utilisation.

GhostGambler
la source
J'ai lu ailleurs que le UPDATE table SET counter = counter + 1est suffisamment atomique? Avez-vous toujours besoin des relevés de transaction qui l'entourent?
CMCDragonkai
@CMCDragonkai Votre requête seule est atomique, mais si vous sélectionnez la valeur avant et que vous n'avez pas utilisé FOR UPDATEet de transactions, la valeur que vous avez sélectionnée peut être différente de celle qui a été utilisée dans la requête de mise à jour. Ma combinaison de requêtes verrouille la ligne dès que la valeur est sélectionnée et garantit donc que cette valeur de compteur exacte sera utilisée dans la requête de mise à jour.
GhostGambler
D'accord, mais cela n'est nécessaire que si je fais un travail autre que l'incrémentation, n'est-ce pas? En l'état, une seule requête de mise à jour atomique est suffisante si c'est tout ce que je veux faire?
CMCDragonkai
1
@CMCDragonkai Si vous n'exécutez pas une autre requête qui touche la colonne, vous êtes bon.
GhostGambler