Может ли Lost Update произойти на уровне изоляции фиксации чтения в PostgreSQL?

У меня есть запрос, как показано ниже в PostgreSQL:

UPDATE 
     queue 
SET 
  queue.status   = 'PROCESSING' 
WHERE 
    queue.status   = 'WAITING' AND
    queue.id       = (SELECT id FROM queue WHERE STATUS = 'WAITING' LIMIT 1 )
RETURNING 
        queue.id

и многие работники пытаются обрабатывать одну работу за раз (поэтому у меня есть подзапрос с пределом 1). После этого обновления каждый работник получает информацию об идентификаторе и обрабатывает работу, но иногда он получает одну и ту же работу и обрабатывает ее дважды или более. Уровень изоляции - Read Committed.

Мой вопрос: как я могу гарантировать, что одна работа будет обработана один раз? Я знаю, что существует очень много постов, но я могу сказать, что перепробовал большинство из них, и это не помогло ();

  • Я попытался SELECT FOR UPDATE, но это вызвало тупиковую ситуацию.
  • Я пробовал pg_try_advisory_xact_lock, но это вызвано нехваткой общей памяти
  • Я пытался добавить AND pg_try_advisory_xact_lock(queue.id) к предложению WHERE внешнего запроса, но... [?]

Любая помощь будет оценена.

3 ответа

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

То, что произойдет в приведенном выше примере, состоит в том, что, если (скажем) 10 рабочих запущены одновременно, все 10 из них выполнят подзапрос и получат одинаковый идентификатор. Все они попытаются заблокировать этот идентификатор. Один из них добьется успеха; другие заблокируют первый замок. Как только первый бэкэнд фиксируется или откатывается, остальные 9 будут бороться за блокировку. Получите его, перепроверьте предложение WHERE и увидите, что queue.status test больше не совпадает и возвращает без изменения каких-либо строк. То же самое произойдет и с другими 8. Таким образом, вы использовали 10 запросов для выполнения одного запроса.

Если вы не можете явно проверить UPDATE В результате вы увидите, что нулевые строки были обновлены. Вы можете подумать, что получаете потерянные обновления, но это не так. У вас просто есть ошибка параллелизма в вашем приложении, вызванная неправильным пониманием порядка выполнения и правил изоляции. Все, что на самом деле происходит, это то, что вы эффективно сериализуете свои бэкэнды, так что только один за раз действительно продвигается вперед.

Единственный способ, которым PostgreSQL мог бы избежать того, чтобы все они получили одинаковый идентификатор элемента очереди, - это их сериализация, поэтому он не начал выполнять запрос № 2 до тех пор, пока запрос #1 не завершится. Если вы хотите, вы можете сделать это LOCKВ таблице очередей... но опять же, у вас может быть только один рабочий.

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

Вы пытаетесь создать рабочую очередь, используя СУБД. Это не будет работать хорошо. Это будет медленно, это будет больно, и сделать это правильно и быстро будет очень, очень трудно. Не катай свои собственные. Вместо этого используйте хорошо зарекомендовавшую себя, хорошо проверенную систему для надежной постановки задач в очередь. Посмотрите на RabbitMQ, ZeroMQ, Apache ActiveMQ, Celery и т. Д. Есть также PGQ от Skytools, решение на основе PostgreSQL.

Связанные с:

SKIP LOCKEDможно использовать для реализации очереди в PostgreSql. видеть

В PostgreSQL потерянное обновление происходит вино если вы используете SELECT FOR UPDATE в READ COMMITTEDи READ UNCOMMITTED, потерянного обновления не бывает.

Кроме того, потерянное обновление не происходит в REPEATABLE READи SERIALIZABLEнезависимо от того, используете ли вы SELECT FOR UPDATE. *Ошибка возникает, если есть потерянное условие обновления .

Другие вопросы по тегам