Что такое advisory locks в PostgreSQL и когда их использовать?
Что такое advisory locks
Advisory locks — это блокировки уровня приложения, которые не связаны ни с какими таблицами, строками или объектами базы данных напрямую. PostgreSQL просто резервирует идентификатор блокировки (bigint или пара int) и даёт вам примитив мьютекса/семафора поверх соединения с базой. Сама СУБД не проверяет, что стоит за этим числом — это ответственность приложения: вы сами договариваетесь, что, например, число 12345 означает "блокировка на обработку заказа №12345".
В отличие от row-level и table-level блокировок, advisory locks не ограничивают доступ к данным — они блокируют логику, которую вы сами определяете.
Уровни: session и transaction
PostgreSQL предоставляет две группы функций:
pg_advisory_lock(key)/pg_advisory_unlock(key)— держится до явного снятия или до конца сессии (session-level);pg_advisory_xact_lock(key)— держится до конца транзакции (COMMIT/ROLLBACK), снимается автоматически.
Для обоих вариантов есть неблокирующие версии pg_try_advisory_lock / pg_try_advisory_xact_lock, которые сразу возвращают false, если блокировка занята, вместо ожидания. Также есть shared-варианты (_shared) для случаев "много читателей — один писатель".
Ключ может быть одним bigint или парой (int, int) — удобно кодировать namespace + id, например (hashtext('orders'), order_id).
Когда применять
- Распределённые задачи/крон-джобы: гарантировать, что параллельные инстансы приложения не запустят одну и ту же фоновую задачу одновременно (типичный паттерн — pgtryadvisory_lock перед запуском джобы).
- Защита от гонок, когда нет подходящей физической строки для блокировки (например, лок на уровне бизнес-ключа, а не существующей записи).
- Однократные операции — миграции схемы, сериализация деплоя между несколькими подами.
- "Один воркер за раз" в очередях задач, простой rate limiting.
Главное преимущество — распределённая блокировка без отдельной таблицы и усложнения схемы, автоматически освобождающаяся при разрыве соединения (в отличие от, например, Redis-локов, где нужен TTL).
Особенности и подводные камни
- Session-level локи переживают транзакции — если забыть вызвать unlock, лок останется до закрытия соединения. При использовании пулеров (PgBouncer в transaction mode) это опасно: соединение может уйти к другому клиенту с висящим локом.
- Advisory locks видны в
pg_locks(типadvisory), их можно мониторить и снимать вручную черезpg_advisory_unlock_all(). - Блокировки не реплицируются между узлами и не участвуют в MVCC — это чисто in-memory структура на конкретном инстансе Postgres.
- Возможны коллизии хэша: если использовать одну функцию (например, hashtext) для разных сущностей без namespace, могут случайно столкнуться несвязанные домены.
- В transaction-режиме pgbouncer session-level locks практически непригодны — используйте xact-level варианты.
Что хочет услышать интервьюер
Понимание, что advisory locks не связаны с данными таблиц и требуют дисциплины на уровне приложения
Чёткое разделение session-level и transaction-level локов и их разного времени жизни
Знание неблокирующих (try) и shared вариантов функций
Практические кейсы: дедупликация крон-джобов, сериализация миграций, распределённые worker'ы
Осведомлённость о рисках при использовании с connection pooler'ами (PgBouncer transaction mode)
Пример: Дедупликация фоновой задачи через session-level lock
-- Пытаемся захватить блокировку для конкретной задачи (например, id = 42)
SELECT pg_try_advisory_lock(42) AS locked;
-- Если locked = true, выполняем работу джобы...
-- После завершения обязательно снимаем блокировку
SELECT pg_advisory_unlock(42);
Пример: Transaction-level lock с namespace через хэш
BEGIN;
-- Блокировка автоматически снимется при COMMIT или ROLLBACK
SELECT pg_advisory_xact_lock(hashtext('orders')::int, 12345);
-- Критическая секция: безопасно обновляем связанные данные
UPDATE orders SET status = 'processing' WHERE id = 12345;
COMMIT;
Типичные ошибки
Путают advisory locks с row-level locking (SELECT ... FOR UPDATE) и не понимают, что они не блокируют доступ к данным
Забывают вызвать pg_advisory_unlock при использовании session-level локов, что приводит к вечно висящим блокировкам
Используют session-level локи вместе с transaction-режимом PgBouncer, где соединение может передаться другому клиенту вместе с локом
Не задумываются о коллизиях хэша при генерации ключа из строки без namespace
Не знают о существовании неблокирующих (try_) и shared-версий функций и всегда используют блокирующий вариант


