Ce que vous voulez, c'est SELECT ... FOR UPDATE dans le contexte d'une transaction. SELECT FOR UPDATE met un verrou exclusif sur les lignes sélectionnées, comme si vous exécutiez UPDATE. Il s'exécute également implicitement dans le niveau d'isolement READ COMMITTED, quel que soit le niveau d'isolation explicitement défini. Sachez simplement que SELECT ... FOR UPDATE est très mauvais pour la concurrence et ne doit être utilisé qu'en cas de nécessité absolue. Il a également tendance à se multiplier dans une base de code au fur et à mesure que les gens copient et collent.
Voici un exemple de session de la base de données Sakila qui montre certains des comportements des requêtes FOR UPDATE.
Tout d'abord, pour que tout soit clair, définissez le niveau d'isolement des transactions sur REPEATABLE READ. Ceci n'est normalement pas nécessaire, car il s'agit du niveau d'isolement par défaut pour InnoDB:
session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Dans l'autre session, mettez à jour cette ligne. Linda s'est mariée et a changé de nom:
session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
De retour en session1, parce que nous étions en REPEATABLE READ, Linda est toujours LINDA WILLIAMS:
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Mais maintenant, nous voulons un accès exclusif à cette ligne, nous appelons donc FOR UPDATE sur la ligne. Notez que nous récupérons maintenant la version la plus récente de la ligne, qui a été mise à jour dans la session2 en dehors de cette transaction. Ce n'est pas RÉPÉTABLE LIRE, c'est LIRE ENGAGÉ
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | BROWN |
+------------+-----------+
1 row in set (0.00 sec)
Testons le verrou défini dans session1. Notez que session2 ne peut pas mettre à jour la ligne.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Mais nous pouvons toujours en choisir
session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address |
+-------------+------------+-----------+------------+-------------------+
| 3 | LINDA | BROWN | 7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)
Et nous pouvons toujours mettre à jour une table enfant avec une relation de clé étrangère
session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session1> COMMIT;
Un autre effet secondaire est que vous augmentez considérablement votre probabilité de provoquer un blocage.
Dans votre cas spécifique, vous souhaitez probablement:
BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;
Si l'élément "faire d'autres choses" n'est pas nécessaire et que vous n'avez pas réellement besoin de conserver des informations sur la ligne, alors SELECT FOR UPDATE est inutile et inutile et vous pouvez simplement exécuter une mise à jour:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
J'espère que cela a du sens.
items
WHEREstatus
= 'en attente' LIMIT 1 FOR UPDATE;" et ils voient tous les deux la même ligne, puis l'un verrouille l'autre. J'espérais en quelque sorte qu'il pourrait contourner la ligne verrouillée et passer à l'élément suivant qui était en attente ..