Дедлоки при update, insert
Как снизить риск дедлоков при INSERT / UPDATE в SQL Server
Дедлок (взаимная блокировка) возникает, когда две или более транзакций удерживают блокировки, необходимые друг другу, и ни одна не может завершиться. Для операций INSERT и UPDATE дедлоки – обычное явление, особенно при параллельной работе с одними и теми же диапазонами данных или индексами.
Основные причины дедлоков для INSERT/UPDATE
- Разный порядок доступа к ресурсам (таблицам, индексам, страницам).
- Блокировки на некластеризованных индексах – при
INSERT/UPDATEблокируются все затронутые индексы, порядок их обновления может различаться между транзакциями. - Обновление ключевых столбцов – может вызывать перемещение строки на другую страницу и блокировки сразу двух страниц.
- Слишком большие/долгие транзакции – удерживают блокировки дольше нужного.
- Повышенный уровень изоляции (
SERIALIZABLE,REPEATABLE READ).
Практические способы снижения риска
1. Обеспечьте одинаковый порядок доступа ко всем ресурсам
Золотое правило: внутри всех транзакций обращайтесь к таблицам и индексам в одном и том же порядке.
❌ Плохо: одна транзакция обновляет Orders → OrderItems, другая – OrderItems → Orders.
✅ Хорошо: везде сначала 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. Получение null при конкатенации (объединении) строк
SQL. Проблема с доступом к таблице БД
Сколько памяти использует SQL Server
Высокое значение Resource Monitor в sp_who2 (загрузка CPU больше 50%)
Дополнительный заработок для разработчиков на T-SQL
- Шаг 1. Создать концепт проекта
- Шаг 2. Получить оценку бюджета (КП)
- Шаг 3. Заключить договор
- Шаг 4. Создать совместно техническое задание
- Шаг 5. Поэтапная реализация проекта