Как избавиться от управляемых взаимоблокировок при создании подчиненных документов из управляемой формы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработчики систем на платформе 1С:Предприятие 8.3 часто сталкиваются с аппаратными и управляемыми взаимоблокировками (deadlocks). Особое недоумение вызывают ситуации, когда блокировка накладывается на регистр, к которому в модуле формы нет явных обращений, а технологический журнал или специализированные сервисы мониторинга (например, сервис Гилёва) упорно указывают на то, что «виновником» является именно управляемая форма документа. Попытки решить проблему «в лоб» — например, установкой исключительной управляемой блокировки на весь регистр в обработчике ОбработкаПроведения — не только не помогают, но и существенно снижают общую производительность системы.

В этой статье мы подробно разберем природу этого явления, выясним, почему стандартные подходы не работают, и рассмотрим три надежных архитектурных сценария для полного устранения управляемых взаимоблокировок.

Почему сервис мониторинга указывает на управляемую форму?

Давайте проанализируем ситуацию: в модуле формы отсутствуют обработчики событий ПередЗаписью или ПриЗаписи, но система мониторинга утверждает, что блокировка началась именно в форме. Почему так происходит?

Дело в том, что когда пользователь нажимает на кнопку (например, «Оформить связанные документы»), на сервере запускается процедура, в рамках которой происходит запись текущего (основного) документа и параллельно создаются, записываются или проводятся другие, подчиненные документы.

Если вся эта цепочка действий выполняется внутри одного серверного вызова, платформа 1С автоматически открывает неявную транзакцию. Все блокировки, накладываемые подчиненными документами на свои регистры накопления или сведений, привязываются к этой транзакции. Инструменты анализа фиксируют контекст начала всей транзакции. Поскольку она стартовала из-за интерактивного действия в форме, именно управляемая форма регистрируется как точка зарождения блокировки.

Почему блокировка всего регистра в ОбработкеПроведения — это критическая ошибка?

Когда разработчики пытаются устранить взаимоблокировку установкой исключительной блокировки на весь регистр в коде проведения, они совершают грубый антипаттерн по следующим причинам:

  1. Запоздалая инициализация. Блокировка в обработчике ОбработкаПроведения накладывается слишком поздно. К моменту начала проведения основного документа транзакция уже открыта, и другие процессы (или создание подчиненных документов) могли успеть наложить хаотичные блокировки на СУБД.
  2. Нарушение порядка захвата ресурсов. Если параллельно работают два пользователя, процессы начинают запрашивать ресурсы в разной последовательности:
    – Транзакция 1 (Пользователь 1): Создает связанный Документ Б (блокирует Регистр 2) -> Затем проводит Документ А (пытается заблокировать Регистр 1).
    – Транзакция 2 (Пользователь 2): Проводит Документ А напрямую (блокирует Регистр 1) -> Пытается заблокировать Регистр 2.
    В результате возникает классический тупик (deadlock). Установка блокировки на весь регистр просто выстраивает пользователей в очередь и резко увеличивает время ожидания, приводя к ошибкам таймаута.

Для гарантированного решения проблемы нам необходимо разорвать длинную транзакцию и разнести запись основного документа и создание подчиненных по разным транзакционным блокам. Рассмотрим три практических способа реализации этой задачи.

Вариант А. Разделение транзакций (перенос логики на клиентскую сторону)

Этот способ является самым простым и эффективным, если создание подчиненных документов происходит быстро. Мы должны принудительно завершить транзакцию записи основного документа, вернуть управление на клиента, а затем начать новую, независимую транзакцию для создания подчиненных объектов.

Рассмотрим пример реализации логики в модуле управляемой формы документа:


&НаКлиенте
Процедура КомандаСоздатьСвязанныеДокументы(Команда)

    // Шаг 1. Записываем текущий документ. 
    // Это действие открывает и сразу же закрывает первую короткую транзакцию.
    ПараметрыЗаписи = Новый Структура("ЗаписатьВФорме", Истина);
    Если Не Записать(ПараметрыЗаписи) Тогда
        Возврат; // Если основной документ не записался, прекращаем процесс
    КонецЕсли;

    // Шаг 2. Вызываем серверную процедуру для генерации подчиненных документов.
    // Это будет уже вторая, полностью независимая транзакция.
    СоздатьСвязанныеОбъектыНаСервере();

    ПоказатьОповещениеПользователя("Успешно", , "Связанные документы успешно созданы!");

КонецПроцедуры

&НаСервере
Процедура СоздатьСвязанныеОбъектыНаСервере()
    
    // Явно открываем транзакцию для обеспечения целостности создания пакета документов
    НачатьТранзакцию();
    Попытка
        
        // Создаем подчиненный документ
        НовыйДокумент = Документы.РасходныйКассовыйОрдер.СоздатьДокумент();
        НовыйДокумент.Дата = ТекущаяДатаСеанса();
        НовыйДокумент.ДокументОснование = Объект.Ссылка;
        // Заполнение остальных реквизитов...
        
        НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
        
        ЗафиксироватьТранзакцию();
        
    Исключение
        ОтменитьТранзакцию();
        ЗаписатьЛогОшибки(ОписаниеОшибки());
        ВызватьИсключение "Не удалось создать подчиненные документы: " + КраткоеПредставлениеОшибки(ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

Вариант Б. Асинхронное выполнение через фоновые задания

Если процесс генерации подчиненных документов ресурсоемкий (например, рассчитываются сложные себестоимости или создается сразу десяток связанных документов), выполнять его в основном потоке нельзя. Пользователь столкнется с зависанием интерфейса, а СУБД захватит таблицы на длительное время. Здесь на помощь приходят фоновые задания.

Рекомендуем использовать механизмы библиотеки стандартных подсистем (БСП), а именно общий модуль ДлительныеОперации. Схема выглядит следующим образом:

  1. Пользователь нажимает кнопку на форме.
  2. Программа записывает текущий документ (завершая транзакцию).
  3. Программа запускает фоновое задание на сервере, которое в фоновом режиме создает и проводит нужные документы.
  4. Интерфейс пользователя остается свободным, а длительные транзакции не блокируют интерактивную работу.

Посмотрим на пример кода в модуле формы:


&НаКлиенте
Процедура СоздатьАсинхронно(Команда)

    // Записываем текущий документ
    Если Не Записать() Тогда
        Возврат;
    КонецЕсли;

    // Подготавливаем параметры для фонового задания
    ПараметрыПроцедуры = Новый Структура;
    ПараметрыПроцедуры.Вставить("ДокументСсылка", Объект.Ссылка);

    // Запускаем фоновое задание через БСП
    РезультатФонового = ДлительныеОперации.ЗапуститьВыполнениеНаСервере(
        УникальныйИдентификатор,
        "МойОбщийМодульСервер.СоздатьСвязанныеДокументыФоновоеЗадание",
        ПараметрыПроцедуры,
        "Создание связанных документов"
    );

    // Открываем форму ожидания для пользователя
    ПараметрыФормыОжидания = Новый Структура;
    ПараметрыФормыОжидания.Вставить("ИдентификаторЗадания", РезультатФонового.ИдентификаторЗадания);
    
    ОткрытьФорму("ОбщаяФорма.ДлительнаяОперация", ПараметрыФормыОжидания, ЭтотОбъект);

КонецПроцедуры

Вариант В. Организация очереди через вспомогательный регистр

Для высоконагруженных систем (Highload) оптимальным решением является использование паттерна «Очередь сообщений». Вместо мгновенного создания документов мы фиксируем лишь само намерение их создания.

Алгоритм настройки выглядит так:

  1. Создаем регистр сведений ОчередьСозданияДокументов с одним измерением (ссылка на документ-источник).
  2. При проведении или записи основного документа мы просто делаем быструю запись в этот регистр. Это занимает миллисекунды и практически не создает блокировок.
  3. Настраиваем регламентное задание, которое запускается раз в минуту (или чаще) и выполняет обработку очереди по следующему алгоритму:

Процедура ОбработатьОчередьСозданияДокументов() Экспорт

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |   Очередь.ДокументИсточник КАК ДокументИсточник
        |ИЗ
        |   РегистрСведений.ОчередьСозданияДокументов КАК Очередь";

    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();

    Пока Выборка.Следующий() Цикл
        
        НачатьТранзакцию();
        Попытка
            // Создаем подчиненные документы
            СоздатьПодчиненныеДокументы(Выборка.ДокументИсточник);
            
            // Удаляем запись из очереди, чтобы не обрабатывать повторно
            НаборЗаписей = РегистрыСведений.ОчередьСозданияДокументов.СоздатьНаборЗаписей();
            НаборЗаписей.Отбор.ДокументИсточник.Установить(Выборка.ДокументИсточник);
            НаборЗаписей.Записать();
            
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            ЗаписатьОшибкуВЛог(Выборка.ДокументИсточник, ОписаниеОшибки());
        КонецПопытки;
        
    КонецЦикла;

КонецПроцедуры

Дополнительные системные настройки для борьбы с блокировками

Помимо архитектурного разделения транзакций, крайне важно соблюдать базовые правила конфигурирования СУБД в 1С:

Применяя данные подходы, мы гарантированно избавимся от управляемых взаимоблокировок, разгрузим СУБД и обеспечим комфортную и быструю работу пользователей в формах документов.

← На главную