← Назад к вопросам

Как возникает дедлок в базе данных?

3.0 Senior🔥 201 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как возникает дедлок в базе данных?

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

Основные механизмы возникновения дедлока в базе данных

Дедлок возникает из-за четырёх обязательных условий, известных как условия Кофмана:

  1. Условие взаимного исключения (Mutual Exclusion). Ресурс (например, строка в таблице) может использоваться только одной транзакцией в данный момент.
  2. Условие захвата и ожидания (Hold and Wait). Транзакция, уже имеющая ресурс, пытается захватить новый, освобождая текущий.
  3. Условие отсутствия вытеснения (No Preemption). Ресурс нельзя принудительно освободить от транзакции, которая его держит.
  4. Условие цикличного ожидания (Circular Wait). Транзакции образуют замкнутый цикл, где каждая ожидает ресурс, захваченный следующей в этом цикле.

Типичные сценарии в SQL (пример)

Рассмотрим классический пример с двумя транзакциями T1 и T2, работающими с таблицей users.

-- Транзакция T1
BEGIN TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- Транзакция T2
BEGIN TRANSACTION;
UPDATE users SET balance = balance - 50 WHERE id = 2;
UPDATE users SET balance = balance + 50 WHERE id = 1;
COMMIT;

Последовательность действий, приводящая к дедлоку:

  • Шаг 1: T1 выполняет первый UPDATE и блокирует строку id=1.
  • Шаг 2: T2 выполняет первый UPDATE и блокирует строку id=2.
  • Шаг 3: T1 пытается выполнить второй UPDATE для строки id=2, но она уже заблокирована T2. T1 переходит в состояние ожидания.
  • Шаг 4: T2 пытается выполнить второй UPDATE для строки id=1, но она уже заблокирована T1. T2 переходит в состояние ожидания.

Таким образом, возникает циклическое ожидание: T1 ждёт ресурс от T2, а T2 ждёт ресурс от T1. База данных обнаруживает эту ситуацию и, согласно своему алгоритму deadlock detection, вынужденно прерывает одну из транзакций (обычно наименее затратную для отката), чтобы разрешить конфликт.

Распространённые причины в веб-приложениях

В реальных PHP-приложениях дедлоки часто возникают из-за следующих факторов:

  • Неоптимальный порядок запросов в транзакциях. Как показано в примере выше, если разные части приложения выполняют обновления в разном порядке, вероятность дедлока резко возрастает. Решение — стандартизация последовательности операций.
  • Длительные транзакции. Транзакция, которая долго держит блокировки (например, при сложных вычислениях или взаимодействии с внешними API между запросами), увеличивает "окно конфликта".
  • Отсутствие индексов. Когда UPDATE или DELETE выполняются по условию, которое не использует индекс, база данных может блокировать гораздо больше строк (или даже всю таблицу), повышая шансы конфликта.
-- Плохо: может привести к блокировке большого количества строк
UPDATE orders SET status = 'processed' WHERE customer_id = 123 AND status = 'new';

-- Хорошо: если есть индекс на (customer_id, status), блокировка будет целевой
  • Разные уровни изоляции транзакций. Использование высоких уровней изоляции, таких как SERIALIZABLE, приводит к более агрессивному блокированию и может способствовать дедлокам.

Как предотвращать дедлоки в практике Backend PHP-разработчика

  1. Стандартизация порядка операций. В рамках проекта договориться о порядке изменения связанных сущностей.
  2. Минимизация времени жизни транзакции. Выполнять бизнес-логику вне транзакции, а внутри — только необходимые SQL-запросы. Не делать внешние вызовы внутри открытой транзакции.
  3. Корректное использование индексов. Все WHERE условия в операциях изменения данных должны использовать эффективные индексы.
  4. Выбор оптимального уровня изоляции. Часто достаточно READ COMMITTED. Для критичных операций можно использовать SELECT ... FOR UPDATE для явной блокировки нужных строк заранее.
  5. Обработка ошибок дедлока в коде. База данных (MySQL, PostgreSQL) возвращает специфическую ошибку при обнаружении дедлока. Транзакцию необходимо перезапустить.
// Пример обработки дедлока в PHP (с использованием PDO и MySQL)
$retryCount = 0;
$maxRetries = 3;

do {
    try {
        $pdo->beginTransaction();
        // ... выполнение бизнес-логики и запросов ...
        $pdo->commit();
        break; // Успех, выход из цикла
    } catch (\PDOException $e) {
        // MySQL код ошибки для дедлока: 1213
        if ($e->getCode() == 1213 && $retryCount < $maxRetries) {
            $retryCount++;
            $pdo->rollBack();
            // Можно добавить небольшую паузу
            usleep(100 * $retryCount);
            continue; // Попробовать ещё раз
        } else {
            // Логируем критическую ошибку или выбрасываем исключение
            throw $e;
        }
    }
} while ($retryCount < $maxRetries);

Итог: Дедлок — это системная проблема конкуренции за ресурсы, возникающая из-за конкретных условий взаимодействия транзакций. Его понимание и профилактика требуют внимания к порядку запросов, времени блокировок и структуре данных. Грамотная стратегия включает как превентивные меры (индексы, порядок операций), так и реактивные (повторные попытки в коде приложения).