Quello che vuoi è SELEZIONARE ... PER AGGIORNARE dal contesto di una transazione. SELECT FOR UPDATE inserisce un blocco esclusivo nelle righe selezionate, proprio come se si eseguisse UPDATE. Funziona anche implicitamente nel livello di isolamento READ COMMITTED indipendentemente da ciò che il livello di isolamento è esplicitamente impostato su. Basta essere consapevoli del fatto che SELECT ... FOR UPDATE è molto dannoso per la concorrenza e deve essere utilizzato solo quando assolutamente necessario. Ha anche la tendenza a moltiplicarsi in una base di codice mentre le persone tagliano e incollano.
Ecco una sessione di esempio dal database Sakila che illustra alcuni comportamenti delle query FOR UPDATE.
Innanzitutto, solo così siamo cristallini, imposta il livello di isolamento della transazione su REPEATABLE READ. Questo di solito non è necessario, poiché è il livello di isolamento predefinito per 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)
Nell'altra sessione, aggiorna questa riga. Linda si sposò e cambiò nome:
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
Nella sessione 1, perché eravamo in REPEATABLE READ, Linda è ancora 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)
Ma ora, vogliamo l'accesso esclusivo a questa riga, quindi chiamiamo FOR UPDATE sulla riga. Si noti che ora otteniamo la versione più recente della riga indietro, che è stata aggiornata nella sessione2 al di fuori di questa transazione. Non è REPEATABLE READ, è READ COMMITTED
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)
Testiamo il blocco impostato in session1. Si noti che session2 non può aggiornare la riga.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Ma possiamo ancora selezionare da esso
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)
E possiamo ancora aggiornare una tabella figlio con una relazione di chiave esterna
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 altro effetto collaterale è che aumenti notevolmente la probabilità di provocare un deadlock.
Nel tuo caso specifico, probabilmente vuoi:
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;
Se il pezzo "fai qualche altra cosa" non è necessario e in realtà non è necessario mantenere le informazioni sulla riga intorno, quindi SELEZIONA PER AGGIORNAMENTO è inutile e dispendioso e puoi invece semplicemente eseguire un aggiornamento:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Spero che ciò abbia un senso.
items
WHEREstatus
= 'pending' LIMIT 1 FOR UPDATE;" ed entrambi vedono la stessa riga, quindi uno bloccherà l'altro. Speravo in qualche modo che fosse in grado di bypassare la fila bloccata e andare al prossimo oggetto che era in sospeso ..