Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с крайне коварной ошибкой: «В данной транзакции уже происходили ошибки!». Коварство этой проблемы заключается в том, что стек вызова указывает на абсолютно безобидный участок кода — например, на чтение константы, получение ссылки или простой запрос. При этом реальная причина сбоя произошла гораздо раньше и в совершенно другом месте.
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм «поломанной транзакции», почему стандартный отладчик часто молчит и как пошагово найти истинного виновника проблемы.
Для того чтобы понять природу этой ошибки, нам необходимо заглянуть на уровень взаимодействия платформы 1С и систем управления базами данных (СУБД), таких как MS SQL Server или PostgreSQL. Любая транзакция в базе данных должна обладать свойством атомарности: либо все изменения записываются успешно, либо все они полностью откатываются.
Проанализируем классический сценарий, приводящий к сбою:
док.Записать(РежимЗаписиДокумента.Проведение)).Попытка ... Исключение.Попытка успешно «гасит» ошибку в коде 1С, предотвращая аварийное завершение работы программы в этот конкретный момент.Исключении).Константы.ИспользоватьВерсионированиеОбъектов.Получить() или записи другого объекта) СУБД наотрез отказывается работать и выбрасывает платформенное исключение: «В данной транзакции уже происходили ошибки!».Таким образом, модуль версионирования объектов или любой другой механизм чтения/записи, на котором споткнулась программа, абсолютно ни при чем. Они лишь первыми попытались обратиться к СУБД после того, как транзакция уже была безвозвратно испорчена предыдущей «тихой» ошибкой.
Часто разработчики недоумевают: почему при ручном проведении того же самого документа в интерактивном режиме или при запуске внешней обработки на сервере никаких ошибок не возникает? Выделим две основные причины:
Во-первых, различие в границах транзакций. В регламентных заданиях (особенно предназначенных для группового перепроведения документов) разработчики часто объединяют обработку целого пакета документов в одну большую транзакцию для ускорения работы или обеспечения согласованности данных. При интерактивной же работе каждый документ проводится в своей собственной, очень короткой транзакции. Если ошибка происходит там, она сразу прерывает процесс и откатывает транзакцию, выводя пользователю понятное и локализованное сообщение (например, «Недостаточно товара на складе»).
Во-вторых, различие в правах и контексте выполнения. Регламентные задания выполняются от имени специального пользователя (как правило, с полными правами) и в фоновом режиме на сервере, где могут срабатывать специфические подписки на события или проверки, неактивные при обычном сеансе.
Если вы попытаетесь отловить эту ошибку стандартным запуском отладки, то столкнетесь с тем, что конфигуратор упорно игнорирует первоначальное место сбоя. Выясним, почему это происходит:
Попытка ... Исключение, отладчик по умолчанию считает её «обработанной» программно и не останавливает на ней выполнение. Остановка происходит только на финальной ошибке обращения к поломанной транзакции, которая уже неинформативна.Рассмотрим подробнее три эффективных метода, которые помогут вам быстро найти место возникновения первичного сбоя.
Для того чтобы поймать ошибку непосредственно в момент её возникновения в регламентном задании, выполните следующие шаги:
Попытка.Если база данных работает в клиент-серверном режиме под высокой нагрузкой и отладка «на лету» затруднительна, лучшим инструментом становится Технологический Журнал 1С. Разберем, как его настроить:
Создайте или отредактируйте файл конфигурации техножурнала logcfg.xml на сервере 1С, добавив в него сбор событий EXCP (исключительные ситуации) и событий СУБД (например, DBMSSQL или DBPOSTGRS). Пример простой настройки:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\v8_logs\errors" history="2">
<event>
<eq property="Name" value="EXCP"/>
</event>
<event>
<eq property="Name" value="DBMSSQL"/>
<like property="Context" value="%Ошибка%"/>
</event>
<property name="all"/>
</log>
</config>
После воспроизведения ошибки откройте полученные файлы логов. Найдите запись об ошибке «В данной транзакции уже происходили ошибки!» и начните прокручивать лог назад (вверх). Буквально на несколько строк выше вы увидите реальное исключение СУБД с подробным описанием проблемы.
Если у вас есть подозрение, в каком именно общем модуле или обработчике (например, ПриЗаписи, ОбработкаПроведения) происходит сбой, можно временно закомментировать конструкции Попытка...Исключение вокруг методов записи или проведения объектов. В этом случае транзакция упадет штатно, и система сразу запишет в Журнал регистрации подробный и понятный стек ошибки с указанием точной строки кода.
Для того чтобы в будущем не допускать подобных ситуаций в собственной разработке, важно соблюдать железное правило стандартов разработки 1С:
Никогда не используйте конструкцию Попытка-Исключение внутри транзакции без последующего немедленного её отката!
Посмотрим на пример неправильного кода, который гарантированно приведет к ошибке поломанной транзакции при возникновении любого сбоя записи:
НачатьТранзакцию();
Для Каждого ТекДок Из СписокДокументов Цикл
Попытка
ТекДок.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Ошибка перехвачена, транзакция СУБД сломана, но мы продолжаем цикл!
ЗаписатьВЖурналРегистрации("Ошибка проведения документа " + ТекДок);
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию(); // Здесь или на следующей итерации все упадет
А теперь проанализируем правильный шаблон реализации групповой обработки. Если один из документов пакета не провелся, мы обязаны сразу же отменить всю транзакцию целиком:
НачатьТранзакцию();
Успешно = Истина;
Для Каждого ТекДок Из СписокДокументов Цикл
Попытка
ТекДок.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Успешно = Ложь;
Прервать; // Немедленно выходим из цикла обработки в транзакции
КонецПопытки;
КонецЦикла;
Если Успешно Тогда
ЗафиксироватьТранзакцию();
Иначе
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ВызватьИсключение "Групповое проведение отменено из-за ошибки в одном из документов!";
КонецЕсли;
Применяя эти несложные архитектурные правила и владея методикой правильной настройки отладчика фоновых заданий, вы сможете локализовать и устранять любые транзакционные ошибки в регламентных заданиях за считанные минуты.