Дедлоки при update, insert

Время чтения - 10 мин.Дата публикации 26.05.2026

Как снизить риск дедлоков при INSERT / UPDATE в SQL Server

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

Основные причины дедлоков для INSERT/UPDATE

  • Разный порядок доступа к ресурсам (таблицам, индексам, страницам).
  • Блокировки на некластеризованных индексах – при INSERT/UPDATE блокируются все затронутые индексы, порядок их обновления может различаться между транзакциями.
  • Обновление ключевых столбцов – может вызывать перемещение строки на другую страницу и блокировки сразу двух страниц.
  • Слишком большие/долгие транзакции – удерживают блокировки дольше нужного.
  • Повышенный уровень изоляции (SERIALIZABLE, REPEATABLE READ).

Практические способы снижения риска

1. Обеспечьте одинаковый порядок доступа ко всем ресурсам

Золотое правило: внутри всех транзакций обращайтесь к таблицам и индексам в одном и том же порядке.

Плохо: одна транзакция обновляет OrdersOrderItems, другая – OrderItemsOrders.

Хорошо: везде сначала Orders, потом OrderItems.

2. Используйте подсказки блокировок (только если уверены)

  • UPDLOCK – при выборке строк, которые планируете обновить, захватывайте обновляющие блокировки, а не разделяемые. Это предотвращает классический deadlock «read-update».
  • HOLDLOCK (или SERIALIZABLE) расширяет диапазон блокировок – используйте с осторожностью.
  • NOLOCK (READ UNCOMMITTED) – только для чтения, для обновлений не применим.
BEGIN TRAN
SELECT * FROM Inventory WITH (UPDLOCK, ROWLOCK)
WHERE ProductId = 123;
-- обновляем на основе прочитанного
UPDATE Inventory SET Quantity = Quantity - 10 WHERE ProductId = 123;
COMMIT

3. Сократите время транзакции

  • Не держите транзакцию открытой во время ввода-вывода или взаимодействия с пользователем.
  • Выносите операции, не требующие атомарности, за пределы BEGIN TRAN...COMMIT.
  • Разбивайте большие пакеты INSERT/UPDATE на порции (например, по 1000 строк) с короткими транзакциями.

4. Правильно проектируйте индексы

  • Добавьте некластеризованные индексы по столбцам, используемым в WHERE для UPDATE или для поиска обновляемых строк.
  • Для INSERT – избегайте избыточных индексов (каждый индекс требует блокировок при вставке).
  • Используйте FILLFACTOR для кластеризованных индексов, чтобы уменьшить разбиение страниц.

5. Снизьте уровень изоляции (если допускают бизнес-требования)

Включите READ COMMITTED SNAPSHOT ISOLATION (RCSI) на уровне базы данных. Читающие не блокируют пишущих, а пишущие не блокируют читающих. Это сильно снижает дедлоки.

ALTER DATABASE YourDB SET READ_COMMITTED_SNAPSHOT ON;

Используйте SNAPSHOT ISOLATION для длинных отчётов – они не будут конфликтовать с обновлениями.

6. Обрабатывайте дедлоки на уровне приложения (retry)

Обязательно реализуйте повтор (retry) с ожиданием:

int retries = 3;
while (retries-- > 0)
{
    try
    {
        ExecuteCommand();
        break;
    }
    catch (SqlException ex) when (ex.Number == 1205) // deadlock victim
    {
        Thread.Sleep(new Random().Next(10, 200));
    }
}

7. Оптимизируйте сами запросы

  • Для UPDATE используйте WHERE с индексированными столбцами.
  • Старайтесь не обновлять столбцы, входящие в кластеризованный ключ.
  • Избегайте UPDATE или INSERT внутри триггеров, которые делают дополнительные выборки из тех же таблиц.

8. Для массовых INSERT используйте меньше блокировок

INSERT INTO Target WITH (TABLOCK) (Col1, Col2)
SELECT ...

Используйте INSERT ... SELECT ... ORDER BY, чтобы вставка шла в порядке кластеризованного ключа.

9. Мониторинг и анализ

  • Расширенные события (Extended Events) – ловят deadlock graph.
  • sys.dm_tran_locks – посмотреть текущие блокировки.
  • Флаг трассировки 1222 или 1204 – выводят deadlock graph в ERRORLOG.
  • SET DEADLOCK_PRIORITY LOW для неважных транзакций.

Пример: снижение дедлока при UPDATE

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
    DECLARE @qty int
    SELECT @qty = Quantity FROM Inventory WITH (UPDLOCK, ROWLOCK)
    WHERE ProductId = 123 AND WarehouseId = 456

    UPDATE Inventory SET Quantity = @qty - 10
    WHERE ProductId = 123 AND WarehouseId = 456
COMMIT

Памятка (кратко)

Метод Эффективность Сложность
Одинаковый порядок доступа Высокая Низкая
UPDLOCK / HOLDLOCK Средняя Средняя
RCSI (Read Committed Snapshot) Очень высокая Низкая (вкл. на БД)
Короткие транзакции Высокая Средняя
Retry логика Обязательна Низкая

Самый эффективный и простой способ – включить RCSI (ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON;). Он решает большинство дедлоков между чтением и записью и не требует изменения кода приложений.


Дополнение: почему ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON выполняется долго и что он делает

Команда ALTER DATABASE aichat SET READ_COMMITTED_SNAPSHOT ON; предназначена для включения на уровне базы данных версионного механизма изоляции READ COMMITTED (RCSI). Это один из самых эффективных способов снизить дедлоки между чтением и записью. Однако сама команда иногда выполняется неожиданно долго. Разберёмся, почему.

🐌 Почему команда выполняется так долго?

Короткий ответ: команда не выполняется, а ждёт. Сама операция включения RCSI занимает доли секунды. Но SQL Server должен дождаться, пока из базы данных выйдут все остальные пользователи и процессы. Если в момент выполнения команды есть активные подключения (например, работают приложения, запущены отчёты, открыты сессии в SSMS), ваша команда встаёт в очередь и «висит» в ожидании эксклюзивного доступа к базе данных.

Это сделано для того, чтобы не разрывать транзакции пользователей принудительно (если только вы не используете опцию WITH ROLLBACK IMMEDIATE).

⚙️ Что конкретно делает эта команда внутри?

Чтобы понять важность изменения, нужно знать, как работают чтения в стандартном режиме (RCSI OFF) и в новом (RCSI ON).

  • До включения RCSI (OFF)блокирующее чтение: SELECT устанавливает разделяемые блокировки (Shared locks) на читаемые строки. Они блокируют UPDATE/INSERT/DELETE на этих строках до завершения чтения, создавая конкуренцию и дедлоки.
  • После включения RCSI (ON)версионное чтение без блокировок: SQL Server начинает работать с версиями строк, хранящимися в tempdb. Каждый запрос SELECT получает согласованный снимок данных на момент его начала (без блокировок). Читатели не блокируют писателей, писатели не блокируют читателей – дедлоки между чтением и записью практически исчезают.

💡 Как ускорить выполнение команды или обойти ожидание?

Есть два основных способа.

Способ 1: Элегантное ожидание (самый безопасный, рекомендуемый для production)
Команда выполнится сама, как только база данных станет свободной. Вы можете узнать, какие процессы блокируют выполнение:

-- Посмотреть все активные соединения с вашей БД
SELECT session_id, login_name, status 
FROM sys.dm_exec_sessions 
WHERE database_id = DB_ID('aichat');

-- Узнать, что именно выполняет блокирующий процесс (замените 55 на реальный SPID)
DBCC INPUTBUFFER(55);

После анализа можно аккуратно завершить только проблемные сессии (например, через KILL 55;), но делайте это только если уверены, что это не критичный процесс.

Способ 2: Принудительное завершение (только в часы обслуживания, когда можно разорвать все соединения)
Добавьте опцию WITH ROLLBACK IMMEDIATE – она откатит все незавершённые транзакции и разорвёт все соединения, после чего мгновенно применит настройку.

ALTER DATABASE aichat SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

⚠️ Важно: этот вариант может привести к ошибкам в приложениях, если они не рассчитаны на внезапный разрыв соединений. Используйте только в окно технических работ.

🔍 Как проверить, что RCSI успешно включён?

После успешного выполнения команды (или если хотите проверить текущий статус), выполните запрос:

SELECT name, is_read_committed_snapshot_on 
FROM sys.databases 
WHERE name = 'aichat';  -- или используйте DB_NAME() для текущей БД

Если в столбце is_read_committed_snapshot_on значение 1 – RCSI активен.

Итог: Долгое выполнение команды – это признак того, что база данных занята. Ничего страшного, просто подождите или используйте ROLLBACK IMMEDIATE, если это безопасно. RCSI – это мощный и простой способ резко снизить количество дедлоков, особенно в базах с интенсивным чтением и записью.

Насколько полезной была статья?

Что еще посмотреть по SQL Server

SQL Management Studio медленно работает, тормозит. Как решить проблему

Ошибки в SQL запросах и хранимых процедурах

Не запускается Configuration Manager

Решение проблем MS SQL Server с блокировками

Решение ошибки Cannot resolve the collation conflict between

SQL. Ошибка. Transaction (Process ID) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

SQL. Получение null при конкатенации (объединении) строк

SQL. Проблема с доступом к таблице БД

SQL Server. Ошибка Table:String or binary data would be truncated. The statement has been terminated.

Сколько памяти использует SQL Server

Высокое значение Resource Monitor в sp_who2 (загрузка CPU больше 50%)

Проблема Не получается сделать take offline для базы данных

Дополнительный заработок для разработчиков на T-SQL

Прямая работа с заказчиками как ИП или самозанятый. Нужно знать только SQL и HTML.
Falcon Space - платформа для создания сайтов с личными кабинетами
В 2-3 раза экономнее и быстрее, чем заказная разработка
Более гибкая, чем коробочные решения и облачные сервисы
Используйте готовые решения и изменяйте под свои потребности
Запрос расчета стоимости веб-проекта на базе Falcon Space
Если видео Youtube плохо грузится, то попробуйте найти видео в ВК видео на канале Falcon Space
Сайт использует Cookie, Яндекс Метрику. Используя сайт, вы соглашаетесь с правилами сайта. См. Правила конфиденциальности и Правила использования сайта OK