Как имитировать взаимоблокировку в PostgreSQL?

Я новичок в PostgreSQL. Я хочу имитировать взаимоблокировку для этого расписания:
Изображение образца данных

Как имитировать взаимоблокировку в PostgreSQL? Это вообще возможно? Как заблокировать конкретный столбец?

BEGIN;
UPDATE deadlock_demonstration
SET salary1=(SELECT salary1 
FROM deadlock_demonstration
WHERE worker_id = 1 
FOR UPDATE)+100
WHERE worker_id=1;
SELECT pg_sleep(5);
commit;
SELECT salary2 FROM deadlock_demonstration WHERE worker_id = 1 FOR UPDATE;

На другом экране я запустил это:

BEGIN;
UPDATE deadlock_demonstration
SET salary2=(SELECT salary1 
FROM deadlock_demonstration
WHERE worker_id = 1
FOR UPDATE)+200
WHERE worker_id=1;
SELECT pg_sleep(5);
commit;
SELECT salary1 FROM deadlock_demonstration WHERE worker_id = 1 FOR UPDATE;

Почему тупик не происходит? Можете ли вы дать предложение, что я должен изменить, чтобы стимулировать взаимоблокировку?


person user3388473    schedule 01.04.2014    source источник


Ответы (2)


  1. Откройте два соединения параллельно, например два экземпляра psql или два окна запросов в pgAdmin (у каждого своя сессия).
  2. Запустите транзакцию в каждом соединении. BEGIN;
  3. Запускайте взаимно конфликтующие команды по очереди.
  4. Прежде чем вы сможете зафиксировать, один из двух будет откатан с исключением взаимоблокировки.
  5. Вы можете откатить другой. ROLLBACK;

Явная блокировка таблиц выполняется так же просто, как:

LOCK tbl;

Блокировка строк может быть выполнена с помощью:

SELECT * FROM tbl WHERE boo = 3 FOR UPDATE;

Или FOR SHARE и т. д. Подробности в руководстве.
(Или неявно с UPDATE или DELETE.)

Пример

Ваш добавленный пример не может зайти в тупик. Оба пытаются сначала взять одну и ту же блокировку на одну и ту же строку одной и той же таблицы. Второй будет ждать, пока первый закончит.

Пример фактического создания взаимоблокировки (строки должны существовать, иначе блокировка не будет установлена):

Transaction 1                    Transaction 2
BEGIN;
                                 BEGIN;
SELECT salary1 
FROM   deadlock_demonstration
WHERE  worker_id = 1
FOR    UPDATE;
                                 SELECT salary1 
                                 FROM   deadlock_demonstration
                                 WHERE  worker_id = 2
                                 FOR    UPDATE;
UPDATE deadlock_demonstration
SET    salary1 = 100
WHERE  worker_id = 2;

                                 UPDATE deadlock_demonstration
                                 SET    salary1 = 100
                                 WHERE  worker_id = 1;

                    --> ... ???? deadlock!

Результат

Пользователь OP3388473 предоставил этот снимок экрана после проверки решения:

Скриншот, воспроизводящий это в psql

person Erwin Brandstetter    schedule 01.04.2014
comment
Спасибо за ваш ответ! Я выполнил 2 запроса (см. в отредактированном вопросе). 2 запроса дают одну и ту же ошибку: ОШИБКА: в PL/pgSQL не разрешено начинать/завершать транзакцию СОВЕТ: Используйте блок BEGIN с ИСКЛЮЧЕНИЕМ. КОНТЕКСТ: функция PL/pgSQL transaction3(), строка 11, оператор SQL-оператор. Означает ли это, что тупик произошел? - person user3388473; 01.04.2014
comment
Вы не можете сделать это внутри функции PL/pgSQL. Функция всегда выполняется внутри транзакции как единого целого. Вам нужны простые команды SQL. - person Erwin Brandstetter; 01.04.2014
comment
Я вставил простую команду sql (см. отредактированный вопрос). Но почему-то тупика не происходит. Я понятия не имею, почему. Что я делаю неправильно? - person user3388473; 01.04.2014
comment
Я пробовал этот код в одном sql-запросе, а затем в 2 экранах запросов. Результат возвращается через 13-14 мс. Тупика не случилось. - person user3388473; 01.04.2014
comment
@user3388473. Работает на меня. Я тестировал. Вы должны что-то упустить. Две отдельные транзакции (два сеанса psql или два отдельных окна запросов в pgAdmin). Выполняйте команды по очереди, а не все сразу. Ссылочные строки должны существовать! Или никакой замок не будет взят. - person Erwin Brandstetter; 01.04.2014
comment
@ErwinBrandstetter ты говоришь по-русски? Скриншот. - person Nick; 25.04.2018
comment
@Ник: Нджет. :) ОП предоставил скриншот после того, как проверил мое решение. - person Erwin Brandstetter; 25.04.2018
comment
@ErwinBrandstetter Это все еще работает в 2018 году? Мой второй экземпляр просто зависает и ждет первого после FOR UPDATE;. Он снова начинает отвечать, как только я завершаю первую транзакцию. - person Cramps; 23.07.2018
comment
@Cramps: по-прежнему верно в последних версиях (и вряд ли когда-либо изменится). Вам нужны взаимно конфликтующие команды, как показано. В вашем случае второй экземпляр должен заблокировать что-то, что первый экземпляр пытается заблокировать взамен. - person Erwin Brandstetter; 23.07.2018
comment
Я понимаю. Я выполнил один и тот же запрос на обоих. Я пробовал BEGIN; SELECT * FROM table FOR UPDATE; и BEGIN; SELECT * FROM table WHERE id = 1 FOR UPDATE;, и оба приводят к тому, что второй экземпляр зависает до тех пор, пока первый не завершится (например, зафиксирован). Есть идеи, почему? (скомпилирован Postgres из исходников на Mac) - person Cramps; 23.07.2018
comment
@Cramps: выполнение одних и тех же запросов в обоих сеансах не может привести к взаимоблокировке (пока они блокируют строки детерминированным образом). На самом деле это рекомендуемая стратегия для избежания взаимоблокировок (пример: dba.stackexchange.com/a/195220/ 3684). Попробуйте мой пример выше, чтобы создать взаимоблокировку. - person Erwin Brandstetter; 23.07.2018

Означает ли это, что произошла взаимоблокировка?

Нет. Это означает то, что написано: вы не можете использовать commit в pgsql, ясно сказано здесь.

person Str.    schedule 01.04.2014
comment
с/pgsql/plpgsql. - person Erwin Brandstetter; 14.12.2017