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