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