JavaRush /Курсы /SQL SELF /Ограничения и потенциальные проблемы при работе с транзак...

Ограничения и потенциальные проблемы при работе с транзакциями: LOCK, DEADLOCK

SQL SELF
54 уровень , 1 лекция
Открыта

Транзакции в PostgreSQL обеспечивают изоляцию — одну из ключевых характеристик ACID. Для этого система использует блокировки. Блокировка — это механизм, гарантирующий, что несколько транзакций не "сражаются" за одну и ту же запись или таблицу. Представьте себе очередь в супермаркете: на кассе обслуживается один человек за раз. Блокировки работают примерно так же, только они управляют доступом к данным в таблицах.

Основные виды блокировок

В PostgreSQL есть несколько типов блокировок, некоторые из которых вы, возможно, уже встречали в EXPLAIN ANALYZE:

  1. ROW EXCLUSIVE (строчная эксклюзивная блокировка) — возникает при изменении данных в строке. Это наиболее часто используемый тип блокировки, например, при выполнении INSERT, UPDATE или DELETE.
  2. SHARE (общая блокировка) — используется для операций, которые гарантируют, что данные не изменятся, пока выполняется запрос, например, при выполнении SELECT ... FOR SHARE.
  3. EXCLUSIVE (эксклюзивная блокировка) — предотвращает любые другие операции с данными, кроме чтения.

Можно сказать, что блокировки — это эдакие сторожевые псы, которые охраняют наши данные.

Проблема блокировок: что может пойти не так?

Теперь, когда мы понимаем, что такое блокировки, давайте посмотрим, какие проблемы они могут вызвать. Самая большая из них — это проблема взаимоблокировок. Взаимоблокировка (или deadlock) — это ситуация, в которой две (или более) транзакции ждут друг друга, из-за чего выполнение застревает в бесконечном цикле. Типично это выглядит так:

  1. Транзакция 1 блокирует строку A и хочет получить доступ к строке B.
  2. Транзакция 2 блокирует строку B и хочет получить доступ к строке A.
  3. Так как обе строки заблокированы, ни одна из транзакций не может завершиться.

Пример взаимоблокировки

Чтобы почувствовать боль программиста, столкнувшегося с deadlock, представьте такой сценарий:

-- Транзакция 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Одновременно
-- Транзакция 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- Транзакция 1 пытается заблокировать id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- Транзакция 2 пытается заблокировать id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

Вуаля! Каждая транзакция заблокирована другой. PostgreSQL рано или поздно это обнаруживает и выбрасывает ошибку взаимоблокировки.

Как избежать взаимоблокировок?

  1. Последовательный порядок доступа к данным. Старайтесь всегда обращаться к данным в одном и том же порядке. Если транзакция 1 сначала блокирует строку A, а затем строку B, то транзакция 2 должна следовать той же логике: сначала строка A, потом строка B.
-- Правильный порядок доступа
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. Минимизируйте продолжительность транзакций. Долгие транзакции увеличивают вероятность взаимоблокировки. Завершайте транзакции как можно быстрее: BEGIN, выполняйте все нужные изменения и сразу же COMMIT.

  2. Используйте уровни изоляции транзакций. Если ваша задача не требует строгой изоляции данных, рассмотрите возможность использования уровня изоляции READ COMMITTED. Он позволяет избежать некоторых блокировок.

Тайм-ауты и диагностика блокировок

Существуют инструменты для диагностики и предотвращения блокировок в PostgreSQL. Например, можно установить максимальное время ожидания блокировки:

SET lock_timeout = '5s';  -- Устанавливаем таймаут блокировки в 5 секунд

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

Отслеживание блокировок в PostgreSQL

Одна из полезных команд для мониторинга блокировок — это pg_locks. Она показывает все активные блокировки в системе:

SELECT * FROM pg_locks;

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

Особенности LOCK и ручная блокировка объектов

Если вам нужно вручную управлять блокировками, используйте команду LOCK:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

Внимание: ACCESS EXCLUSIVE — самая сильная блокировка, она запрещает любые другие операции с таблицей, даже SELECT. Используйте ее только для особых случаев (например, изменения структуры таблицы).

Для блокировки отдельных строк при изменении используйте SELECT ... FOR UPDATE:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

Это гарантирует, что другие транзакции не смогут изменить эти строки до конца вашей транзакции.

Что стоит помнить о блокировках?

Блокировки — это не зло, а инструмент. Они помогают сохранять целостность данных, но требуют внимательного обращения. Вот несколько ключевых советов:

  1. Не начинайте транзакцию, если точно не знаете, зачем она вам.
  2. Завершайте транзакцию как можно быстрее.
  3. Избегайте доступа к данным в разном порядке.
  4. Используйте уровни изоляции, подходящие для вашей задачи.

Теперь вы готовы к работе с блокировками и взаимоблокировками! Да пребудет с вами pg_locks и мудрость ACID.

2
Задача
SQL SELF, 54 уровень, 1 лекция
Недоступна
Создание и просмотр блокировок
Создание и просмотр блокировок
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ra Уровень 35 Student
21 августа 2025
Можно ли писать в транзакции (нам же не надо получать данные, только заблочить)? SELECT 1 FROM users WHERE user_id = 1 FOR UPDATE; вместо SELECT * FROM users WHERE user_id = 1 FOR UPDATE;