Разработчики систем на платформе 1С:Предприятие 8.3 часто сталкиваются с аппаратными и управляемыми взаимоблокировками (deadlocks). Особое недоумение вызывают ситуации, когда блокировка накладывается на регистр, к которому в модуле формы нет явных обращений, а технологический журнал или специализированные сервисы мониторинга (например, сервис Гилёва) упорно указывают на то, что «виновником» является именно управляемая форма документа. Попытки решить проблему «в лоб» — например, установкой исключительной управляемой блокировки на весь регистр в обработчике ОбработкаПроведения — не только не помогают, но и существенно снижают общую производительность системы.
В этой статье мы подробно разберем природу этого явления, выясним, почему стандартные подходы не работают, и рассмотрим три надежных архитектурных сценария для полного устранения управляемых взаимоблокировок.
Давайте проанализируем ситуацию: в модуле формы отсутствуют обработчики событий ПередЗаписью или ПриЗаписи, но система мониторинга утверждает, что блокировка началась именно в форме. Почему так происходит?
Дело в том, что когда пользователь нажимает на кнопку (например, «Оформить связанные документы»), на сервере запускается процедура, в рамках которой происходит запись текущего (основного) документа и параллельно создаются, записываются или проводятся другие, подчиненные документы.
Если вся эта цепочка действий выполняется внутри одного серверного вызова, платформа 1С автоматически открывает неявную транзакцию. Все блокировки, накладываемые подчиненными документами на свои регистры накопления или сведений, привязываются к этой транзакции. Инструменты анализа фиксируют контекст начала всей транзакции. Поскольку она стартовала из-за интерактивного действия в форме, именно управляемая форма регистрируется как точка зарождения блокировки.
Когда разработчики пытаются устранить взаимоблокировку установкой исключительной блокировки на весь регистр в коде проведения, они совершают грубый антипаттерн по следующим причинам:
ОбработкаПроведения накладывается слишком поздно. К моменту начала проведения основного документа транзакция уже открыта, и другие процессы (или создание подчиненных документов) могли успеть наложить хаотичные блокировки на СУБД.Для гарантированного решения проблемы нам необходимо разорвать длинную транзакцию и разнести запись основного документа и создание подчиненных по разным транзакционным блокам. Рассмотрим три практических способа реализации этой задачи.
Этот способ является самым простым и эффективным, если создание подчиненных документов происходит быстро. Мы должны принудительно завершить транзакцию записи основного документа, вернуть управление на клиента, а затем начать новую, независимую транзакцию для создания подчиненных объектов.
Рассмотрим пример реализации логики в модуле управляемой формы документа:
&НаКлиенте
Процедура КомандаСоздатьСвязанныеДокументы(Команда)
// Шаг 1. Записываем текущий документ.
// Это действие открывает и сразу же закрывает первую короткую транзакцию.
ПараметрыЗаписи = Новый Структура("ЗаписатьВФорме", Истина);
Если Не Записать(ПараметрыЗаписи) Тогда
Возврат; // Если основной документ не записался, прекращаем процесс
КонецЕсли;
// Шаг 2. Вызываем серверную процедуру для генерации подчиненных документов.
// Это будет уже вторая, полностью независимая транзакция.
СоздатьСвязанныеОбъектыНаСервере();
ПоказатьОповещениеПользователя("Успешно", , "Связанные документы успешно созданы!");
КонецПроцедуры
&НаСервере
Процедура СоздатьСвязанныеОбъектыНаСервере()
// Явно открываем транзакцию для обеспечения целостности создания пакета документов
НачатьТранзакцию();
Попытка
// Создаем подчиненный документ
НовыйДокумент = Документы.РасходныйКассовыйОрдер.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДатаСеанса();
НовыйДокумент.ДокументОснование = Объект.Ссылка;
// Заполнение остальных реквизитов...
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьЛогОшибки(ОписаниеОшибки());
ВызватьИсключение "Не удалось создать подчиненные документы: " + КраткоеПредставлениеОшибки(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Если процесс генерации подчиненных документов ресурсоемкий (например, рассчитываются сложные себестоимости или создается сразу десяток связанных документов), выполнять его в основном потоке нельзя. Пользователь столкнется с зависанием интерфейса, а СУБД захватит таблицы на длительное время. Здесь на помощь приходят фоновые задания.
Рекомендуем использовать механизмы библиотеки стандартных подсистем (БСП), а именно общий модуль ДлительныеОперации. Схема выглядит следующим образом:
Посмотрим на пример кода в модуле формы:
&НаКлиенте
Процедура СоздатьАсинхронно(Команда)
// Записываем текущий документ
Если Не Записать() Тогда
Возврат;
КонецЕсли;
// Подготавливаем параметры для фонового задания
ПараметрыПроцедуры = Новый Структура;
ПараметрыПроцедуры.Вставить("ДокументСсылка", Объект.Ссылка);
// Запускаем фоновое задание через БСП
РезультатФонового = ДлительныеОперации.ЗапуститьВыполнениеНаСервере(
УникальныйИдентификатор,
"МойОбщийМодульСервер.СоздатьСвязанныеДокументыФоновоеЗадание",
ПараметрыПроцедуры,
"Создание связанных документов"
);
// Открываем форму ожидания для пользователя
ПараметрыФормыОжидания = Новый Структура;
ПараметрыФормыОжидания.Вставить("ИдентификаторЗадания", РезультатФонового.ИдентификаторЗадания);
ОткрытьФорму("ОбщаяФорма.ДлительнаяОперация", ПараметрыФормыОжидания, ЭтотОбъект);
КонецПроцедуры
Для высоконагруженных систем (Highload) оптимальным решением является использование паттерна «Очередь сообщений». Вместо мгновенного создания документов мы фиксируем лишь само намерение их создания.
Алгоритм настройки выглядит так:
ОчередьСозданияДокументов с одним измерением (ссылка на документ-источник).
Процедура ОбработатьОчередьСозданияДокументов() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Очередь.ДокументИсточник КАК ДокументИсточник
|ИЗ
| РегистрСведений.ОчередьСозданияДокументов КАК Очередь";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
НачатьТранзакцию();
Попытка
// Создаем подчиненные документы
СоздатьПодчиненныеДокументы(Выборка.ДокументИсточник);
// Удаляем запись из очереди, чтобы не обрабатывать повторно
НаборЗаписей = РегистрыСведений.ОчередьСозданияДокументов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ДокументИсточник.Установить(Выборка.ДокументИсточник);
НаборЗаписей.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьОшибкуВЛог(Выборка.ДокументИсточник, ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Помимо архитектурного разделения транзакций, крайне важно соблюдать базовые правила конфигурирования СУБД в 1С:
БлокировкаДанных, чтобы заблокировать только те записи, по которым реально будут производиться изменения (например, конкретное сочетание номенклатуры, склада и организации).Разрешить разделение итогов. Это позволяет разным сеансам записывать данные в регистр параллельно, избегая конфликтов на физическом уровне таблиц СУБД.Применяя данные подходы, мы гарантированно избавимся от управляемых взаимоблокировок, разгрузим СУБД и обеспечим комфортную и быструю работу пользователей в формах документов.