То, что вы хотите, это SELECT ... FOR UPDATE из контекста транзакции. SELECT FOR UPDATE устанавливает эксклюзивную блокировку выбранных строк, как если бы вы выполняли UPDATE. Он также неявно работает на уровне изоляции READ COMMITTED независимо от того, какой уровень изоляции установлен явно. Просто помните, что SELECT ... FOR UPDATE очень вреден для параллелизма и должен использоваться только тогда, когда это абсолютно необходимо. Он также имеет тенденцию к умножению в кодовой базе по мере того, как люди копируют и вставляют.
Вот пример сеанса из базы данных Sakila, который демонстрирует некоторые поведения запросов FOR UPDATE.
Во-первых, просто для ясности установите уровень изоляции транзакции на REPEATABLE READ. Обычно это не нужно, так как это уровень изоляции по умолчанию для 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)
В другой сессии обновите эту строку. Линда вышла замуж и сменила имя:
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
Вернувшись в сессию1, потому что мы были в REPEATABLE READ, Линда по-прежнему ЛИНДА УИЛЬЯМС:
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)
Но теперь нам нужен эксклюзивный доступ к этой строке, поэтому мы вызываем FOR UPDATE для этой строки. Обратите внимание, что теперь мы получаем самую последнюю версию строки, которая была обновлена в session2 вне этой транзакции. Это не ПОВТОРНАЯ ЧИТАТЬ, это ЧИТАТЬ
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)
Давайте проверим блокировку, установленную в session1. Обратите внимание, что session2 не может обновить строку.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Но мы все еще можем выбрать из него
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)
И мы все еще можем обновить дочернюю таблицу с отношением внешнего ключа
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;
Другой побочный эффект заключается в том, что вы значительно увеличиваете вероятность возникновения тупика.
В вашем конкретном случае вы, вероятно, хотите:
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;
Если часть «делать что-то другое» не нужна, и вам не нужно хранить информацию о строке вокруг, тогда SELECT FOR UPDATE является ненужным и расточительным, и вместо этого вы можете просто запустить обновление:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Надеюсь, в этом есть смысл.
items
WHEREstatus
= 'pending' LIMIT 1 FOR UPDATE;" и они оба увидят один и тот же ряд, тогда один заблокирует другой. Я надеялся, что каким-то образом он сможет обойти заблокированный ряд и перейти к следующему пункту, который ожидал ..