Взаимные блокировки: обнаружение и предотвращение
Разберитесь, как возникают взаимные блокировки, как 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 detectedPostgreSQL обнаруживает взаимные блокировки
Каждый интервал 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 lockedNOWAIT
Немедленно завершайтесь с ошибкой вместо ожидания:
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 — локальная установка не требуется.
Все уроки этого курса
- Свойства ACID и аномалии
- Уровни изоляции: READ COMMITTED, REPEATABLE READ, SERIALIZABLE
- Взаимные блокировки: обнаружение и предотвращение
- Оптимистическая и пессимистическая блокировка