PostgreSQL: обнаружена тупиковая ситуация SELECT FOR UPDATE в транзакции - PullRequest
0 голосов
/ 27 июня 2018

У меня есть следующая схема

ID (PK) | REF_ID | ACTIVE | СТАТУС

ID - первичный ключ

Я использую следующий запрос для выбора и обновления

BEGIN;    
select * from table where ref_id = $1 and is_active is true for update;
UPDATE table set status = $1 where id =$2;
END;

Объяснение для выше

1) Результат запроса select будет использоваться для блокировки всех строк с предоставленным идентификатором ref, и этот результат используется для некоторой бизнес-логики

2) Обновить запрос для обновления STATUS строки, которая является частью того же ref ID

ВЫПУСКА

postgres@machine ERROR:  deadlock detected
postgres@machine DETAIL:  Process 28297 waits for ShareLock on transaction 4809510; blocked by process 28296.
        Process 28296 waits for ShareLock on transaction 4809502; blocked by process 28297.
        Process 28297: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28296: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update 
postgres@machine ERROR:  deadlock detected
postgres@machine DETAIL:  Process 28454 waits for ShareLock on transaction 4810111; blocked by process 28384.
        Process 28384 waits for ShareLock on transaction 4810092; blocked by process 28297.
        Process 28297 waits for AccessExclusiveLock on tuple (113628,5) of relation 16817 of database 16384; blocked by process 28454.
        Process 28454: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28384: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28297: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update

Эта таблица используется в сильно параллельных и распределенных приложениях (100 параллельно с тем же ref_id), и поэтому я хотел избежать распределенной блокировки, выбрав и обновив в той же транзакции. Но я столкнулся с этой ошибкой взаимоблокировки Не знаю, почему явная блокировка не работает.

Ожидаемое поведение состоит в том, что любая другая работа с таким же ссылочным идентификатором должна ждать, если какая-либо другая работа с таким же ссылочным идентификатором получила блокировку

Помогите мне выяснить, чего мне не хватает или другого обходного пути для этого. Мне все еще не ясно даже после явной блокировки и нахождения в транзакции, почему происходит тупик.

1 Ответ

0 голосов
/ 29 июня 2018

Как сказал Лоренц, в этом простом случае вы сможете исключить возможность взаимоблокировки с помощью ORDER BY в вашем запросе блокировки.

тупик возникает, когда, например:

  • Процесс A получает блокировку для строки 1
  • Процесс B получает блокировку для строки 2
  • Процесс A запрашивает блокировку для строки 2 (и ждет, пока B освободит ее)
  • Процесс B запрашивает блокировку для строки 1 (и ожидает, пока A освободит ее)

... И в этот момент процессы будут ждать друг друга вечно (точнее, пока сервер не заметит и не убьет одного из них).

Но если бы оба процесса заранее договорились заблокировать строку 1, а затем строку 2, этого бы не произошло; один процесс все еще ожидает другого, но другой может продолжить.

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

Порядок должен быть однозначным и устойчивым во времени, поэтому сгенерированный первичный ключ идеален (т. Е. Вам следует ORDER BY id).

...