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