Тупик при использовании SELECT FOR UPDATE - PullRequest
0 голосов
/ 19 января 2019

Я заметил, что одновременное выполнение простых и идентичных запросов, подобных

BEGIN;  
SELECT files.data FROM files WHERE files.file_id = 123 LIMIT 1 FOR UPDATE;
UPDATE files SET ... WHERE files.file_id = 123;
COMMIT;

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

 SELECT blockeda.pid AS blocked_pid, blockeda.query as blocked_query, 
 blockinga.pid AS blocking_pid, blockinga.query as blocking_query FROM pg_catalog.pg_locks blockedl
 JOIN pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
 JOIN pg_catalog.pg_locks blockingl     ON(blockingl.transactionid=blockedl.transactionid
 AND blockedl.pid != blockingl.pid)
 JOIN pg_stat_activity blockinga ON blockingl.pid = blockinga.pid
 WHERE NOT blockedl.granted;

, я вижу оба моих идентичных оператора выбора, перечисленных для blocked_pid и blockin_pid для всей продолжительности тупика.

Итак, мой вопрос: нормально ли это для запросов, которые пытаются выбрать одну и ту же строку FOR UPDATE, чтобы вызвать взаимоблокировку?И если да, то какова лучшая стратегия, чтобы избежать взаимоблокировок в этом сценарии?

1 Ответ

0 голосов
/ 19 января 2019

Ваши команды противоречат.

Если определено files.file_id UNIQUE (или PRIMARY KEY), вам не нужно LIMIT 1.И вам вообще не нужна явная блокировка.Просто запустите UPDATE, поскольку во всей транзакции затрагивается только одна строка, тупиковая ситуация невозможна.(Если только нет побочных эффектов от триггеров или правил или задействованных функций.)

Если files.file_id не UNIQUE (как кажется), то UPDATE может влиять на несколько строк в произвольном порядке и толькоодин из них заблокирован, рецепт для тупиков.Тогда более неотложная проблема заключается в том, что запрос не выполняет то, с чего, по-видимому, вы хотите начать.

Наилучшее решение зависит от недостающей информации.Это будет работать:

UPDATE files
SET    ...
WHERE  primary_key_column = (
         SELECT primary_key_column
         FROM   files 
         WHERE  file_id = 123
         LIMIT  1
     --  FOR    UPDATE SKIP LOCKED
         );

Нет BEGIN; и COMMIT;, необходимых для одной команды, в то время как автоматическая фиксация по умолчанию включена.

Возможно, вы захотите добавить FOR UPDATE SKIP LOCKED (или FOR UPDATE NOWAIT) , чтобы пропустить или сообщить об ошибке, если строка уже заблокирована.

И, возможно, вы захотите добавить предложение WHERE, которое позволит избежать повторной обработки одной и той же строки.

Подробнее здесь:

...