Что такое advisory locks в PostgreSQL и когда их использовать?

SeniorPostgreSQL · Backend·Обновлено 4 сентября 2026
Коротко
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-версий функций и всегда используют блокирующий вариант

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее