Взаимоблокировки (deadlock)

Что ответить, когда спрашивают: «Два документа проводятся одновременно и оба зависают с ошибкой блокировки. Что произошло и как это лечить?»

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

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

Как это возникает: классический сценарий

Представим два расходных документа, которые списывают два товара — А и Б — но обрабатывают их в разном порядке.

// Документ 1 (в цикле по своим товарам): сначала А, потом Б
Заблокировать(Товар_А);   // захватил А
// ... тем временем Документ 2 захватил Б ...
Заблокировать(Товар_Б);   // ждёт Б, который держит Документ 2

// Документ 2 (в цикле по своим товарам): сначала Б, потом А
Заблокировать(Товар_Б);   // захватил Б
Заблокировать(Товар_А);   // ждёт А, который держит Документ 1

Пошагово тупик складывается так:

  1. Документ 1 заблокировал товар А.
  2. Документ 2 заблокировал товар Б.
  3. Документ 1 хочет Б — а его держит Документ 2. Ждёт.
  4. Документ 2 хочет А — а его держит Документ 1. Ждёт.
  5. Оба ждут друг друга. СУБД обнаруживает цикл ожидания и откатывает одну транзакцию с ошибкой вида «Конфликт блокировок» / deadlock.

Обратите внимание: сами по себе оба документа корректны. Проблема рождается только из разного порядка захвата одних и тех же ресурсов при параллельной работе.

Главное лекарство — единый порядок доступа

Если все процессы всегда берут ресурсы в одном и том же порядке, тупик математически невозможен: тот, кто первым захватил «младший» ресурс, гарантированно дойдёт до «старшего» — второй просто подождёт в очереди, но не встанет в тупик.

На практике это значит: перед блокировкой упорядочивайте строки табличной части по тому же ключу (например, по ссылке номенклатуры), чтобы любой документ блокировал товары в одинаковой последовательности.

// Единый порядок: сортируем строки перед проведением,
// чтобы ВСЕ документы блокировали номенклатуру одинаково
ТаблицаТоваров = Товары.Выгрузить();
ТаблицаТоваров.Сортировать("Номенклатура");

Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах.Остатки");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;

Для Каждого Строка Из ТаблицаТоваров Цикл
    Элемент.УстановитьЗначение("Номенклатура", Строка.Номенклатура);
    Элемент.УстановитьЗначение("Склад", Склад);
КонецЦикла;

Блокировка.Заблокировать();

Здесь один объект БлокировкаДанных накапливает все нужные значения и ставит блокировку одним вызовом Заблокировать() — это ещё и надёжнее, чем блокировать построчно в цикле, потому что платформа берёт весь набор согласованно.

Как это работает под капотом

Deadlock обнаруживает не 1С, а СУБД (или менеджер управляемых блокировок платформы). Специальный механизм периодически строит граф ожиданий: кто кого ждёт. Если в графе находится цикл — это тупик. Система выбирает «жертву» (обычно транзакцию, которую дешевле откатить), откатывает её и возбуждает исключение. Вторая транзакция после этого спокойно доводит работу до конца.

Важное следствие для ответа: раз одна транзакция откатывается с ошибкой, прикладной код должен быть готов её повторить. В типовых конфигурациях 1С проведение оборачивают в механизм повторных попыток: поймали исключение блокировки — подождали случайный небольшой интервал — попробовали снова. Но повторы лечат симптом; настоящее лечение — единый порядок, чтобы тупиков не было вовсе.

Ещё сценарии deadlock при проведении

  • Разный порядок регистров. Один документ пишет сначала в «Товары», потом в «Взаиморасчёты», другой — наоборот. Держите единый порядок обращения к регистрам во всех проведениях.
  • Эскалация блокировок. Точечная блокировка по значению может «разрастись» до блокировки страницы/диапазона, если строк много или нет подходящего индекса — и внезапно пересечься с чужой. Индексы (измерения регистра) снижают риск.
  • Долгая транзакция. Чем дольше держится блокировка, тем выше шанс, что кто-то в неё упрётся. Не делайте внутри транзакции ничего лишнего — тем более обращений к пользователю.

Как отвечать на собеседовании

«Deadlock — это когда две транзакции захватили по ресурсу и ждут ресурс друг друга; ожидание бесконечно, поэтому СУБД откатывает одну из них. Возникает из-за разного порядка захвата одних и тех же данных. Главное средство — единый порядок обращения к ресурсам: сортировать строки перед блокировкой и одинаково упорядочивать доступ к регистрам, тогда тупик невозможен. Плюс держать транзакции короткими и уметь повторить откаченную транзакцию». Такой ответ показывает, что вы понимаете и причину, и профилактику, и обработку.

Частые ошибки на собеседовании

  • Путать deadlock с обычным ожиданием блокировки. Долгое ожидание — это ещё не тупик; тупик — это взаимное ожидание по кругу. Разница принципиальна.
  • «Лечится увеличением таймаута». Нет: больший таймаут только оттягивает откат, цикл всё равно не разрешится. Таймаут спасает от долгого ожидания, но не от тупика.
  • Забыть про единый порядок. Кандидат называет deadlock, но не может предложить профилактику — это и есть тот ответ, который отличает знающего от заучившего термин.
  • Игнорировать повтор транзакции. Даже при единой дисциплине блокировок редкий deadlock возможен, поэтому проведение должно уметь повториться, а не падать пользователю в лицо.

Итоги-шпаргалка

  • Deadlock = взаимное ожидание по кругу; система откатывает одну транзакцию.
  • Причина — разный порядок захвата одних и тех же ресурсов.
  • Лекарство №1 — единый порядок доступа: сортировать строки, одинаково упорядочивать регистры.
  • Транзакции держим короткими; блокируем точечно и по индексам.
  • Откаченную транзакцию нужно уметь повторить — но повтор лечит симптом, а не причину.
Проверьте себя
1. Что является главным средством профилактики взаимоблокировок (deadlock) при проведении документов?
AУвеличить таймаут ожидания блокировки, чтобы транзакции успевали дождаться друг друга
BОбеспечить единый порядок обращения к ресурсам — например, сортировать строки перед блокировкой, чтобы все процессы блокировали данные одинаково
CПолностью отказаться от блокировок и полагаться на автоматический режим СУБД
DПроводить все документы строго последовательно, запретив параллельную работу пользователей
2. Кандидат говорит: «Deadlock лечится увеличением таймаута ожидания блокировки». Что здесь неверно?
AНичего, это корректное решение — больший таймаут даёт транзакциям время договориться
BТаймаут вообще не влияет на блокировки в 1С
CПри deadlock процессы ждут друг друга по кругу бесконечно; больший таймаут лишь оттягивает откат, но цикл ожидания не разрешает
DТаймаут можно менять только в автоматическом режиме блокировок