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

В какой ситуации может возникнуть Deadlock?

2.2 Middle🔥 132 комментариев
#Базы данных и SQL

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

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

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

Что такое Deadlock (Взаимная блокировка)?

Deadlock — это ситуация в многозадачной среде, когда два или более процесса бесконечно ожидают друг друга, поскольку каждый удерживает ресурс, необходимый другому процессу, и при этом сам ожидает освобождения ресурса, удерживаемого другим процессом. Система "зависает" без возможности прогресса.

Условия возникновения Deadlock

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

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

Типичные ситуации Deadlock в PHP Backend

1. Блокировки в базах данных

Наиболее частый сценарий в веб-приложениях — транзакционные блокировки.

-- Сессия 1
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- Удерживает блокировку строки id=1

-- Сессия 2
START TRANSACTION;
UPDATE users SET balance = balance + 50 WHERE id = 2;
UPDATE users SET balance = balance - 50 WHERE id = 1;
-- Ожидает блокировку строки id=1, которую удерживает Сессия 1

-- Сессия 1 (продолжение)
UPDATE users SET balance = balance + 100 WHERE id = 2;
-- Ожидает блокировку строки id=2, которую удерживает Сессия 2
-- DEADLOCK!

Результат: База данных обнаружит deadlock и откатит одну из транзакций.

2. Конкурентный доступ к файлам

PHP-скрипты могут конфликтовать при работе с одними файлами.

// Скрипт 1
$fp1 = fopen('fileA.txt', 'r+');
flock($fp1, LOCK_EX); // Блокирует fileA
sleep(2);
$fp2 = fopen('fileB.txt', 'r+');
flock($fp2, LOCK_EX); // Ожидает fileB

// Скрипт 2 (запущен параллельно)
$fp2 = fopen('fileB.txt', 'r+');
flock($fp2, LOCK_EX); // Блокирует fileB
sleep(2);
$fp1 = fopen('fileA.txt', 'r+');
flock($fp1, LOCK_EX); // Ожидает fileA
// DEADLOCK!

3. Взаимные вызовы в распределённых системах

В микросервисной архитектуре deadlock может возникнуть на уровне HTTP/RPC-вызовов.

// Сервис A вызывает сервис B
class ServiceA {
    public function processOrder() {
        $lock = $this->acquireLock('order:123');
        $response = $this->httpClient->post('service-b/reserve');
        // Ожидает ответ от Service B
    }
}

// Сервис B вызывает сервис A
class ServiceB {
    public function reserveItem() {
        $lock = $this->acquireLock('item:456');
        $response = $this->httpClient->post('service-a/validate');
        // Ожидает ответ от Service A
    }
}

4. Взаимные блокировки в кеше (Redis/Memcached)

При использовании SETNX или Redlock для распределённых блокировок.

// Процесс 1
$redis->setnx('lock:resource1', 1); // Блокирует resource1
sleep(1);
$locked = $redis->setnx('lock:resource2', 1); // Ожидает resource2

// Процесс 2
$redis->setnx('lock:resource2', 1); // Блокирует resource2
sleep(1);
$locked = $redis->setnx('lock:resource1', 1); // Ожидает resource1

Стратегии предотвращения Deadlock

Проактивные методы:

  • Упорядочивание блокировок — всегда захватывать ресурсы в строго определённом порядке.
  • Таймауты — использовать flock($fp, LOCK_EX | LOCK_NB) или SETNX с таймаутом.
  • Отказ от взаимного исключения — использовать атомарные операции, CAS (Compare-and-Swap).

Реактивные методы:

  • Обнаружение deadlock — системы вроде InnoDB имеют встроенные детекторы.
  • Разрушение deadlock — принудительный откат одной из транзакций (DB делает это автоматически).

Практические рекомендации для PHP-разработчиков

  1. Всегда соблюдайте порядок блокировок в коде:
// Правильно: всегда сначала resource1, потом resource2
$lock1 = $this->lockManager->acquire('resource1');
$lock2 = $this->lockManager->acquire('resource2');

// Опасно: разный порядок в разных местах кода
  1. Используйте таймауты для всех операций блокировки.
  2. Минимизируйте время удержания блокировок — выполняйте минимальную работу внутри критической секции.
  3. Применяйте идемпотентные операции там, где это возможно.
  4. Используйте SELECT ... FOR UPDATE NOWAIT в PostgreSQL или NOWAIT/SKIP LOCKED в MySQL 8+.

Deadlock — не теоретическая проблема, а реальная угроза для высоконагруженных PHP-приложений. Грамотное проектирование транзакций, корректное использование блокировок и понимание механизмов СУБД позволяют снизить вероятность взаимных блокировок до приемлемого уровня.

В какой ситуации может возникнуть Deadlock? | PrepBro