Блокировки в PostgreSQL: как избежать зависаний и дедлоков
Блокировка строки защищает данные от гонки, но неудачная транзакция способна задержать другие запросы и привести к дедлоку. Разбираем практические варианты для PostgreSQL 16: явную блокировку строк, очередь с SKIP LOCKED и advisory locks.

В PostgreSQL 16 два запроса могут одновременно прочитать один остаток товара, а затем оба списать его — если приложение не защитило операцию транзакцией и блокировкой. Но и обратная крайность опасна: транзакция, которая держит блокировку во время сетевого вызова, заставляет другие запросы ждать. Ниже — три практических способа синхронизировать работу и критерии выбора.
Сначала определите, что именно нужно защитить
Для изменения конкретной записи обычно подходит блокировка строки. Для распределения задач между несколькими обработчиками — пропуск уже занятых строк через SKIP LOCKED. Если синхронизировать нужно действие, не привязанное к одной записи, может подойти advisory lock.
Примеры и поведение в этой статье относятся к PostgreSQL 16. Блокировки строк не означают, что вся таблица становится недоступна для чтения: другие транзакции могут продолжать читать данные. Конфликт возникает, когда запросы пытаются несовместимо изменить одну и ту же строку или захватить конфликтующие блокировки.
Термин. Дедлок — ситуация, когда транзакции ждут блокировки друг друга по кругу. PostgreSQL обнаруживает такой цикл и прерывает одну из транзакций; приложению важно уметь повторить всю операцию.
Три подхода и их цена
| Подход | Когда выбирать | Цена и риск |
|---|---|---|
SELECT ... FOR UPDATE | Несколько действий должны согласованно изменить известную запись. | Конкурирующие изменения ждут; длинная транзакция увеличивает задержки. |
FOR UPDATE SKIP LOCKED | Несколько работников разбирают очередь, и им подходят разные задачи. | Запрос пропускает занятые строки, поэтому не гарантирует строгую очередность. |
| Advisory lock | Нужно согласовать операцию по ключу, которому не соответствует одна строка. | Смысл ключа и соблюдение блокировки должны контролировать приложения. |
Подход 1. Заблокируйте строку до изменения
Если значение нужно прочитать, проверить и изменить как одно действие, выполните эти шаги в одной транзакции. Например, перед списанием товара прочитайте остаток с FOR UPDATE, проверьте его и обновите запись. Пока транзакция не завершится, другая транзакция не сможет так же заблокировать эту строку.
BEGIN;
SELECT quantity
FROM inventory
WHERE product_id = 42
FOR UPDATE;
-- Приложение проверяет, что quantity достаточно.
UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 42;
COMMIT;
Это фрагмент для выполнения внутри приложения: проверка остатка должна происходить до обновления, а при недостатке товара транзакцию нужно откатить. В реальном запросе передавайте идентификатор и количество параметрами, а не собирайте SQL строкой. Условие в UPDATE можно дополнительно использовать для проверки остатка, но тогда нужно проверить, сколько строк изменено.
Критически важна граница транзакции. Не вызывайте внутри неё внешний API, не ждите пользователя и не выполняйте медленную работу, не связанную с защищаемой записью. Чем дольше транзакция, тем дольше конкуренты стоят в очереди.
Подход 2. Разбирайте очередь через SKIP LOCKED
Когда несколько работников выбирают задания, обычный выбор с последующей блокировкой может заставить их ждать одну и ту же первую запись. SKIP LOCKED позволяет пропустить строки, уже заблокированные другим работником, и взять следующую доступную. В PostgreSQL 16 это подходит для конкурентной обработки очередей, но не для отчётов, которым нужны согласованные данные.
Следующий запрос выбирает одну готовую задачу, помечает её выполняющейся и возвращает запись. Он должен запускаться как единый SQL-запрос:
WITH candidate AS (
SELECT id
FROM jobs
WHERE status = 'ready'
ORDER BY created_at, id
FOR UPDATE SKIP LOCKED
LIMIT 1
)
UPDATE jobs AS j
SET status = 'running',
started_at = now()
FROM candidate AS c
WHERE j.id = c.id
RETURNING j.id, j.status, j.started_at;
Если запрос не вернул строк, доступных задач сейчас нет. Не считайте это доказательством, что очередь пуста: все подходящие строки могут быть временно заблокированы другими работниками. Периодически проверяйте задания, оставшиеся в статусе выполнения после сбоя, и продумывайте повторную обработку.
Блокировка помогает только пока транзакция короткая, а ошибка конкурентного доступа обработана. Само наличие
FOR UPDATEне делает долгую операцию безопасной.
Подход 3. Используйте advisory lock для логического ресурса
Иногда нужно не дать двум процессам одновременно пересчитать отчёт для одного клиента или запустить одну и ту же фоновую операцию. Если блокировать конкретную строку неудобно, можно использовать транзакционную advisory-блокировку:
BEGIN;
SELECT pg_advisory_xact_lock(12345);
-- Короткая операция, которую нельзя выполнять параллельно.
COMMIT;
pg_advisory_xact_lock принимает ключ типа bigint и удерживает блокировку до конца транзакции. Число в примере — демонстрационный ключ, а не готовая схема идентификаторов. В приложении задайте однозначное соответствие между ресурсом и ключом; все участники, которым нужна взаимная блокировка, должны использовать одинаковое правило. PostgreSQL не связывает этот ключ с таблицей и не проверяет, что другие пути кода тоже берут блокировку.
Как выбирать и что ломается
- Изменяете существующую запись и должны проверить её текущее состояние — блокируйте строку через
FOR UPDATE. - Раздаёте независимые задачи нескольким обработчикам — используйте
SKIP LOCKED, если пропуск занятых записей допустим. - Защищаете действие по логическому ключу, а не конкретную строку, — рассмотрите advisory lock и закрепите единое правило для всех сервисов.
Самые частые проблемы начинаются не с выбора механизма, а с организации транзакций:
- Разный порядок захвата. Два процесса блокируют сначала запись A, потом B, а второй — сначала B, потом A. При одновременном запуске возможен дедлок. Захватывайте набор записей в одинаковом порядке.
- Забыли повтор транзакции. После дедлока PostgreSQL прерывает одну транзакцию. Если операция безопасно повторяема, приложение должно повторить её целиком, а не только последний запрос.
- Транзакция слишком длинная. Внешний вызов или ожидание внутри транзакции удерживает блокировки. Сведите защищаемую часть к минимуму.
- Ожидание без ограничения. Для операций, где долгое ожидание недопустимо, задайте подходящий лимит ожидания блокировки на уровне транзакции или соединения. Значение выбирайте по требованиям сервиса: слишком короткий лимит даст лишние ошибки, слишком длинный — накопит ожидающие запросы.
- Ошибочно ожидать порядок от очереди.
SKIP LOCKEDпропускает занятые строки. Если важен порядок обработки, этот механизм сам по себе его не гарантирует.
В PostgreSQL есть параметры для ограничения ожидания блокировки, но их нельзя назначать вслепую на весь сервер: сначала выясните, сколько запросы могут ждать по требованиям приложения. В продакшене наблюдайте за длительностью транзакций, ожиданиями блокировок и сообщениями о дедлоках. Когда появляется инцидент, сопоставляйте ожидающую и удерживающую транзакции, а не только время ответа клиента.
Что настроить дальше
Начните с одной операции, которая меняет общую запись или распределяет задания. Зафиксируйте границы транзакции, порядок блокировки записей и поведение приложения при прерывании транзакции. Затем проверьте сценарий параллельного запуска и сбоя работника, а в продакшене следите за ожиданиями блокировок и временем транзакций. Изоляция транзакций и инвалидация очереди — отдельные темы; здесь разобраны практические способы синхронизации.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: PostgreSQL 16: Explicit Locking, PostgreSQL 16: SELECT
Частые вопросы
Нужно ли ставить FOR UPDATE перед каждым UPDATE?
Что произойдёт, если возникнет дедлок?
Можно ли использовать SKIP LOCKED для любых списков?
Работают ли эти рекомендации в другой версии PostgreSQL?
Можно ли оставить транзакцию открытой, пока приложение вызывает внешний сервис?
Как понять, что блокировки стали проблемой в продакшене?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.