При разработке высоконагруженных систем на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой «Неустранимого конфликта блокировок» (Deadlock). Типичная ситуация: вы устанавливаете управляемую блокировку по конкретному измерению регистра, а затем пытаетесь прочитать или записать набор записей по регистратору. В этот момент система «замирает» и выдает ошибку. Рассмотрим подробно, почему это происходит и как правильно организовать код, чтобы избежать подобных конфликтов.
Для начала проанализируем ситуацию, описанную в вопросе. Проблема кроется в использовании двух разных пространств блокировок внутри одной транзакции. Давайте выясним, что происходит на уровне платформы:
Блокировка.Добавить("РегистрНакопления.МойРегистр") и устанавливаем значение конкретного измерения (например, Изм1), менеджер управляемых блокировок помечает этот ресурс как занятый.НаборЗаписей и вызываем метод Прочитать(), платформа неявно устанавливает разделяемую (Shared) блокировку на этот регистр, но уже по другому пространству — по Регистратору.Для менеджера блокировок 1С «Измерение1» и «Регистратор» — это два разных, независимых ресурса. Дедлок возникает, когда один сеанс захватил «Измерение1» и ждет «Регистратора», а в это же время другой сеанс (например, при проведении документа) уже захватил «Регистратора» и пытается обратиться к записям, попадающим под блокировку по «Измерению1». Система оказывается в тупике, так как ни один сеанс не может завершить транзакцию.
Самый надежный способ избежать взаимных блокировок — это заявить о своих правах на все необходимые ресурсы одновременно, до того как начнется основная работа с данными. Рассмотрим, как модифицировать исходный код, чтобы объединить блокировки по разным пространствам в один «пакет».
Вместо того чтобы блокировать сначала измерение, а потом неявно регистратор при чтении, мы добавим оба элемента в один объект БлокировкаДанных:
Блокировка = Новый БлокировкаДанных;
// Блокируем пространство по ключевому измерению
ЭлементБлокировкиИзм = Блокировка.Добавить("РегистрНакопления.МойРегистр");
ЭлементБлокировкиИзм.УстановитьЗначение("Изм1", Зн1);
ЭлементБлокировкиИзм.Режим = РежимБлокировкиДанных.Исключительный;
// Блокируем пространство по регистратору (для набора записей)
ЭлементБлокировкиРег = Блокировка.Добавить("РегистрНакопления.МойРегистр");
ЭлементБлокировкиРег.УстановитьЗначение("Регистратор", ВыборкаДетальныеЗаписи.Регистратор);
ЭлементБлокировкиРег.Режим = РежимБлокировкиДанных.Исключительный;
// Устанавливаем блокировку атомарно
Блокировка.Заблокировать();
// Теперь можно безопасно работать с набором
НаборЗаписей = РегистрыНакопления.МойРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ВыборкаДетальныеЗаписи.Регистратор);
НаборЗаписей.Прочитать();
При таком подходе менеджер блокировок либо сразу выделит оба ресурса текущему сеансу, либо заставит его ждать в очереди. Это исключает ситуацию, когда ресурсы захватываются по частям в разном порядке разными сеансами.
Проанализируем еще один важный момент: зачем нам нужно чтение набора записей? Если целью является только получение данных для анализа, то использование НаборЗаписей.Прочитать() внутри транзакции избыточно и опасно, так как этот метод всегда накладывает блокировку на чтение.
Рассмотрим альтернативный вариант: использование запроса. В управляемом режиме блокировок обычный запрос (без предложения ДЛЯ ИЗМЕНЕНИЯ) не накладывает управляемую блокировку. Это позволяет прочитать данные, не вступая в конфликт с параллельными процессами.
Разберем алгоритм действий:
Регистратору.В процессе обсуждения часто упоминается эскалация. Выясним, что это такое. Эскалация — это механизм платформы, при котором множество мелких блокировок (например, по записям) превращаются в одну большую блокировку (на всю таблицу). В 1С это происходит автоматически, если количество заблокированных записей в одном пространстве превышает 100 000.
Хотя в вашем случае записей немного (около 50), эскалация все равно может произойти, если на сервере недостаточно оперативной памяти для хранения таблицы блокировок или если используются старые версии платформы (ниже 8.3.12). Однако чаще всего причиной «неустранимого конфликта» является именно логическое пересечение разных пространств, а не эскалация по количеству.
Чтобы минимизировать вероятность возникновения ошибок в будущем, рекомендуем придерживаться следующих правил:
Таким образом, мы выяснили, что основной причиной дедлока стало чтение набора записей, которое «столкнулось» с ранее установленной ручной блокировкой по измерению. Использование атомарного захвата (через добавление нескольких элементов в один объект БлокировкаДанных) — это наиболее корректный и стабильный способ решения данной задачи.