Скажем, мы выполним...
SELECT * FROM MY_TABLE FOR UPDATE
... и в MY_TABLE больше одной строки.
Теоретически, если две транзакции параллельные выполняют этот оператор, но происходит переход (и, следовательно, блокировка) строк в другом порядке, может возникнуть взаимоблокировка. Например:- Транзакция 1: Заблокирует строку A.
- Транзакция 2: Заблокирует строку B.
- Транзакция 1: попытки заблокировать строки B и блоки.
- Транзакция 2: попытка блокировки строки A и взаимоблокировок.
Способом устранения этого является использование ORDER BY, чтобы гарантировать, что строки всегда заблокированы в том же порядке.
Итак, мой вопрос: будет ли этот теоретический тупик когда-либо на практике? Я знаю, что есть способы искусственно побудить его, но может ли это когда-либо происходить при нормальной работе? Должны ли мы всегда использовать ORDER BY, или это действительно безопасно опустить?
В первую очередь меня интересует поведение Oracle и MySQL/InnoDB, но комментарии к другим СУБД также будут полезны.
--- EDIT ---
Вот как воспроизвести тупик в Oracle, когда порядок блокировки не одинаков:
Создайте тестовую таблицу и заполните ее некоторыми тестовыми данными...
CREATE TABLE DEADLOCK_TEST (
ID INT PRIMARY KEY,
A INT
);
INSERT INTO DEADLOCK_TEST SELECT LEVEL, 1 FROM DUAL CONNECT BY LEVEL <= 10000;
COMMIT;
... с одного сеанса клиента (я использовал SQL Developer), запустите следующий блок:
DECLARE
CURSOR CUR IS
SELECT * FROM DEADLOCK_TEST
WHERE ID BETWEEN 1000 AND 2000
ORDER BY ID
FOR UPDATE;
BEGIN
WHILE TRUE LOOP
FOR LOCKED_ROW IN CUR LOOP
UPDATE DEADLOCK_TEST
SET A = -99999999999999999999
WHERE CURRENT OF CUR;
END LOOP;
ROLLBACK;
END LOOP;
END;
/
От другого сеанса клиента (я просто запустил еще один экземпляр SQL Developer), запустите тот же блок, но с DESC
в ORDER BY
. Через несколько секунд вы получите:
ORA-00060: deadlock detected while waiting for resource
Кстати, вы, скорее всего, достигнете того же результата, полностью удалив ORDER BY
(так что оба блока идентичны), и добавив...
ALTER SESSION SET OPTIMIZER_INDEX_COST_ADJ = 1;
... перед одним блоком, но...
ALTER SESSION SET OPTIMIZER_INDEX_COST_ADJ = 10000;
... перед другим (поэтому Oracle выбирает разные планы выполнения и, вероятно, извлекает строки в другом порядке).
Это иллюстрирует, что блокировка действительно выполняется, поскольку строки извлекаются из курсора (а не для всего набора результатов сразу при открытии курсора).