Що Ви хочете, це ВИБІРИТЕ ... ДЛЯ ОНОВЛЕННЯ в межах транзакції. SELECT FOR UPDATE ставить ексклюзивний замок на вибрані рядки так само, як якщо б ви виконували UPDATE. Він також неявно працює у рівні ізоляції ПРОЧИТАНО ЗАВАНТАЖЕНО незалежно від того, на який явно встановлений рівень ізоляції. Просто майте на увазі, що SELECT ... FOR UPDATE дуже поганий для одночасності, і його слід застосовувати лише при крайній необхідності. Він також має тенденцію до розмноження в кодовій базі під час вирізання та вставки людей.
Ось приклад сеансу з бази даних Sakila, який демонструє деяку поведінку ЗАПИТАННЯ запитів.
По-перше, просто, щоб ми були кристально чистими, встановіть рівень ізоляції транзакцій на ПОТРІБНЕ ЧИТАННЯ. Це, як правило, непотрібно, оскільки це стандартний рівень ізоляції для 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, оскільки ми були у ПОВТОРЕННІ ЧИТАННЯ, Лінда все ще 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)
Але зараз ми хочемо ексклюзивний доступ до цього рядка, тому ми закликаємо ДЛЯ ОНОВЛЕННЯ в рядку. Зауважте, що тепер ми отримуємо останню версію рядка назад, яка була оновлена в 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)
Випробуємо блокування, встановлене в сесії1. Зауважте, що 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;
Якщо фрагмент "зробити якісь інші речі" непотрібний, і вам фактично не потрібно зберігати інформацію про рядок навколо, то ВИБІР ДЛЯ ОНОВЛЕННЯ є непотрібним і марним, і ви можете замість цього просто запустити оновлення:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Сподіваюся, це має певний сенс.
items
WHEREstatus
=" в очікуванні "LIMIT 1 FOR UPDATE;" і вони бачать один і той же ряд, тоді один заблокує другий. Я сподівався якось вдасться обійти заблокований рядок і перейти до наступного пункту, який очікує ..