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