0Pricing
SQL Academy · Урок

Взаимные блокировки: обнаружение и предотвращение

Разберитесь, как возникают взаимные блокировки, как Postgres их обнаруживает, и проектируйте правила порядка блокировок, предотвращающие их

«Взаимные блокировки: обнаружение и предотвращение» — бесплатный урок SQL Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения SQL Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс SQL Academy содержит 4 уроков всего.

Что такое взаимная блокировка

Две транзакции удерживают блокировки, которые нужны друг другу, поэтому ни одна не может продолжить работу. База данных обнаруживает цикл и прерывает одну из транзакций.

Классическая взаимная блокировка

Транзакция A блокирует строку 1, а транзакция B — строку 2. A запрашивает строку 2, B запрашивает строку 1. Работа останавливается.

-- Tx A:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- waiting for B...

-- Tx B:
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
-- waiting for A...

-- ERROR: deadlock detected

PostgreSQL обнаруживает взаимные блокировки

Каждый интервал deadlock_timeout (по умолчанию 1 секунда) PostgreSQL проверяет наличие циклов блокировок. Если цикл найден, одна транзакция прерывается с кодом ошибки 40P01.

ERROR:  deadlock detected
DETAIL:  Process 1234 waits for ShareLock on transaction 5678 ...

Правило порядка блокировок

Решение: всегда устанавливайте блокировки в одном и том же порядке во всех путях выполнения кода.

-- Always update the lower id first:
UPDATE accounts SET balance = balance - 100 WHERE id = LEAST(:from, :to);
UPDATE accounts SET balance = balance + 100 WHERE id = GREATEST(:from, :to);

Взаимные блокировки горячих строк

Частые обновления одних и тех же горячих строк обычно приводят к ожиданию блокировки, а не к взаимным блокировкам. Используйте очереди, разделите горячую строку или выполняйте обновления последовательно в коде приложения.

FOR UPDATE блокирует строки при чтении

Устанавливайте блокировки записи во время чтения, чтобы избежать неожиданностей позже:

BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- both rows locked in id order
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

Пропуск заблокированных строк

Для таблиц очередей используется шаблон «захватить любую доступную строку»:

SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- skips rows other workers have locked

NOWAIT

Немедленно завершайтесь с ошибкой вместо ожидания:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;
-- ERROR: could not obtain lock on row in relation "accounts"

Диагностика взаимных блокировок

Включите log_lock_waits и записывайте контекст взаимной блокировки в журнал. Запись в журнале показывает обе транзакции и их запросы.

Цикл повторных попыток в приложении

Взаимные блокировки можно устранить повторной попыткой — повторите прерванную транзакцию:

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    await runTransaction();
    break;
  } catch (e) {
    if (e.code === '40P01') continue;     // deadlock
    throw e;
  }
}

Уменьшение объема блокировок

Сокращайте транзакции: каждая затронутая строка остается заблокированной до COMMIT. Не выполняйте HTTP-вызовы и длительные вычисления внутри транзакции.

Индексируйте внешние ключи, чтобы избежать эскалации блокировок

При удалении родительской записи проверяется каждая дочерняя строка. Без индекса FK это приводит к полному сканированию таблицы AND блокировкам строк. Индексируйте каждый столбец FK.

Итоги

Взаимные блокировки случаются — проектируйте систему так, чтобы свести их к минимуму.

  • Устанавливайте блокировки в согласованном порядке
  • Используйте FOR UPDATE заранее, чтобы явно обозначить намерение
  • Используйте SKIP LOCKED для очередей
  • Повторяйте попытки при ошибках взаимной блокировки (40P01)
  • Делайте транзакции короткими

Быстрая проверка

Какой принцип проектирования наиболее надежно предотвращает взаимные блокировки?

Часто задаваемые вопросы

Урок «Взаимные блокировки: обнаружение и предотвращение» бесплатный?

Да — полный текст урока «Взаимные блокировки: обнаружение и предотвращение» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс SQL Academy, подпишись на CoddyKit PRO. Курс SQL Academy содержит 4 уроков всего.

Чему я научусь в уроке «Взаимные блокировки: обнаружение и предотвращение»?

Разберитесь, как возникают взаимные блокировки, как Postgres их обнаруживает, и проектируйте правила порядка блокировок, предотвращающие их Ты практикуешь SQL Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать SQL Academy?

Предыдущий опыт не требуется. SQL Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Взаимные блокировки: обнаружение и предотвращение»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке SQL Academy?

Да. Каждый урок SQL Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Свойства ACID и аномалии
  2. Уровни изоляции: READ COMMITTED, REPEATABLE READ, SERIALIZABLE
  3. Взаимные блокировки: обнаружение и предотвращение
  4. Оптимистическая и пессимистическая блокировка
← Назад к SQL Academy