Есть ли у вас опыт работы с ситуациями Deadlock (взаимоблокировкой) в базе данных?

«Есть ли у вас опыт работы с ситуациями Deadlock (взаимоблокировкой) в базе данных?» — вопрос из категории Базы данных, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, сталкивался с анализом и предотвращением deadlock-ов, в основном в SQL Server.

Что такое deadlock? Это ситуация, когда две или более транзакции взаимно блокируют ресурсы (например, строки в таблицах), которые нужны каждой из них для завершения. Ни одна не может продолжить работу, ожидая освобождения ресурса, заблокированного другой. СУБД обнаруживает это и сама "разруливает" конфликт, выбирая одну транзакцию как "жертву" (victim), откатывая её и завершая с ошибкой 1205.

Пример кода с обработкой deadlock на стороне приложения (C#):

int retryCount = 3;
while (retryCount > 0)
{
    try
    {
        using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        using (var connection = new SqlConnection(connectionString))
        {
            await connection.OpenAsync();
            // ... логика с несколькими командами, обновляющими данные ...
            scope.Complete(); // Фиксация транзакции
            break; // Успех, выходим из цикла
        }
    }
    catch (SqlException ex) when (ex.Number == 1205) // Код ошибки deadlock
    {
        retryCount--;
        if (retryCount == 0) throw; // Превышены попытки
        await Task.Delay(100); // Небольшая задержка перед повторной попыткой
        // Логируем ошибку для последующего анализа
        _logger.LogWarning(ex, "Обнаружен deadlock, повторная попытка {Retry}", 3 - retryCount);
    }
}

Стратегии предотвращения (на уровне БД и кода):

  • Единый порядок блокировок: Всегда запрашивать блокировки ресурсов (таблиц, строк) в одинаковом порядке во всем приложении.
  • Короткие транзакции: Выполнять в транзакции только необходимую логику, избегать долгих операций или вызовов внешних сервисов.
  • Изоляция READ COMMITTED SNAPSHOT: Включение этого уровня изоляции на уровне БД может сильно снизить количество блокировок при чтении.
  • Индексы: Оптимизированные индексы уменьшают время сканирования таблиц и, следовательно, время удержания блокировок.
  • NOLOCK / READ UNCOMMITTED: Использовать с осторожностью (допускает "грязное" чтение) только для отчетов, где абсолютная консистентность не критична.
  • Мониторинг: Использовать расширенные события SQL Server (system_health сессия) или запросы к sys.dm_tran_locks для анализа причин deadlock.