В практике разработки на платформе 1С Предприятие нередко возникает загадочная ситуация: разработчик пишет код для программного создания документа (например, документа Событие в конфигурации 1С:Комплексная автоматизация 1.1), запускает его, система отрабатывает без каких-либо видимых ошибок или отказов. Документу даже присваивается очередной уникальный номер. Однако при попытке найти этот документ в списке или через запрос выясняется, что его физически нет в базе данных. При повторном выполнении кода система снова создает документ, присваивает ему тот же самый номер, но объект опять бесследно исчезает.
В этой статье мы подробно проанализируем причины такого поведения платформы 1С, разберем физику процесса взаимодействия с СУБД и рассмотрим готовые практические решения этой проблемы.
Давайте систематизируем ключевые признаки, которые описывают эту техническую проблему:
Записать() отрабатывает без вызова исключений. Свойство Отказ в сопутствующих обработчиках не выставляется в значение Истина.Описанное поведение — это классическое проявление отката транзакции базы данных (Database Transaction Rollback). Давайте разберем этот механизм по шагам, чтобы понять логику работы платформы.
Когда вы вызываете метод Записать() для программно создаваемого объекта, это действие почти всегда происходит в контексте какой-то существующей транзакции. В типовых конфигурациях обычного приложения (таких как КА 1.1 или УПП 1.3) программный код создания подчиненных документов часто помещают внутрь модулей других объектов (например, в обработчики ПриЗаписи, ОбработкаПроведения родительского документа или в подписки на события).
Процесс протекает следующим образом:
Событие.Записать() для нового документа. СУБД временно записывает данные в таблицы базы данных, генерирует для объекта уникальный идентификатор (UUID) и выделяет очередной номер (например, №2).Отказ = Истина в обработчике проведения). Также причиной может стать ошибка СУБД, "заглушенная" разработчиком внутри конструкции Попытка ... Исключение.ROLLBACK (откат). Все изменения, внесенные в БД с момента начала транзакции, полностью аннулируются. Запись о вашем новом документе Событие физически удаляется из таблиц СУБД, а счетчик автонумерации возвращается в исходное состояние.Отдельно проанализируем ситуацию с блоками перехвата ошибок. Многие разработчики совершают критическую ошибку, заворачивая запись объектов в транзакции в конструкцию Попытка ... Исключение:
НачатьТранзакцию();
Попытка
НовыйДокумент = Документы.Событие.СоздатьДокумент();
// ... Заполнение реквизитов ...
НовыйДокумент.Записать();
// Вызов кода, который вызывает ошибку базы данных (например, дублирование уникального индекса)
ВыполнитьНекорректнуюОперациюБД();
ЗафиксироватьТранзакцию();
Исключение
// Ошибка перехвачена, транзакция НЕ отменена явно в коде
ОтменитьТранзакцию(); // Если забыть вызвать эту команду, СУБД все равно сделает "тихий" откат при завершении сеанса
Сообщить("Что-то пошло не так, но мы продолжаем работу...");
КонецПопытки;
Если внутри транзакции происходит любая СУБД-ошибка (например, нарушение уникальности индексов или блокировка таблиц), СУБД помечает всю текущую транзакцию как поврежденную (doomed). Даже если вы перехватили ошибку в блоке Исключение и не дали программе аварийно завершить работу, транзакция уже никогда не сможет быть зафиксирована. Платформа выполнит ее принудительный откат в конце работы процедуры.
Если вы столкнулись с подобной проблемой, выполните следующие шаги для локализации неисправности:
ДокументОбъект.Записать(). В момент остановки откройте окно "Стек вызовов" (Call Stack). Посмотрите, какие процедуры привели к этой точке. Если вы видите в стеке обработчики проведения или записи других объектов, значит, вы гарантированно находитесь внутри транзакции.ТранзакцияАктивна() в табло отладчика. Если функция возвращает значение Истина, ваш документ пишется "под прикрытием" чужой транзакции.Событие в консоли запросов. Это исключит вероятность того, что документ записался, но скрыт от вас какими-то наложенными на форму списка отборами или механизмом RLS (разграничение прав на уровне записей).Рассмотрим два основных архитектурных решения, позволяющих обойти данную проблему и гарантировать успешную запись программно создаваемого документа.
Самый надежный способ гарантировать создание документа — это запускать алгоритм его генерации после того, как родительская транзакция успешно зафиксирована в базе данных СУБД. На управляемых формах для этого идеально подходит событие формы ПослеЗаписи().
Рассмотрим пример реализации на управляемых формах:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
// Родительская транзакция гарантированно завершена и данные сохранены.
// Теперь создание подчиненного документа абсолютно безопасно.
СоздатьСобытиеНаСервере(Объект.Ссылка);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СоздатьСобытиеНаСервере(СсылкаНаИсточник)
НовоеСобытие = Документы.Событие.СоздатьДокумент();
НовоеСобытие.Дата = ТекущаяДата();
НовоеСобытие.ДокументОснование = СсылкаНаИсточник;
НовоеСобытие.Описание = "Событие создано автоматически после успешной записи документа-основания.";
Попытка
НовоеСобытие.Записать(РежимЗаписиДокумента.Запись);
Исключение
// Здесь мы можем безопасно обработать ошибку, если она возникнет
ЗаписьЖурналаРегистрации("ПрограммноеСозданиеСобытия",
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Если создание документа инициируется в коде, который по бизнес-логике обязательно должен находиться в серверном транзакционном модуле (например, в модуле объекта при проведении), и вы не можете перенести вызов на клиентскую форму, используйте механизм фоновых заданий.
Фоновые задания в 1С всегда выполняются в отдельном независимом сеансе на сервере со своим собственным контекстом и, что самое главное, в своей собственной транзакции. Откат основной транзакции никак не затронет фоновое задание.
Разберем, как реализовать этот подход:
// Код внутри модуля проведения основного документа (например, Реализации товаров и услуг)
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Основная логика проведения
// ...
// Нам нужно создать документ "Событие", даже если проведение в итоге откатится.
// Передаем параметры для создания во фоновое задание.
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(ЭтотОбъект.Ссылка);
ПараметрыПроцедуры.Добавить(ЭтотОбъект.Контрагент);
ФоновыеЗадания.Выполнить("ФоновыеПроцедуры.СоздатьСобытиеФоном", ПараметрыПроцедуры);
КонецПроцедуры
Затем в общем модуле ФоновыеПроцедуры (у которого установлены флаги "Сервер" и "Вызов сервера") мы описываем экспортную процедуру создания нашего документа:
Процедура СоздатьСобытиеФоном(СсылкаНаДокумент, Контрагент) Экспорт
// Данный код выполняется в изолированном сеансе вне родительской транзакции.
НовоеСобытие = Документы.Событие.СоздатьДокумент();
НовоеСобытие.Дата = ТекущаяДата();
НовоеСобытие.Партнер = Контрагент;
НовоеСобытие.ДокументОснование = СсылкаНаДокумент;
Попытка
НовоеСобытие.Записать(РежимЗаписиДокумента.Запись);
Исключение
ЗаписьЖурналаРегистрации("ФоновоеСозданиеСобытия",
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Если в вашей практике разработки на 1С программно создаваемые объекты "исчезают" из базы данных, оставляя "битые" ссылки в Журнале регистрации и возвращая номера автонумерации обратно, всегда ищите незафиксированную или откатившуюся транзакцию. Вынос логики создания объектов за пределы транзакции (через клиентские события ПослеЗаписи() или асинхронное выполнение с помощью фоновых заданий) — проверенный стандарт разработки, гарантирующий целостность и сохранность ваших данных.