При работе с крупными обменами данными или сложными бизнес-процессами в 1С программисты часто сталкиваются с крайне неинформативной ошибкой: «В данной транзакции уже происходили ошибки!». Ситуация осложняется тем, что платформа выдает это сообщение не в момент возникновения реальной проблемы, а позже — когда транзакция уже «сломана», но код пытается продолжить работу с базой данных. В этой статье мы подробно разберем природу этой ошибки и научимся находить «виновника» — конкретный объект, из-за которого вся загрузка пошла прахом.
Прежде чем переходить к практике, нам важно понять теорию. Ошибка «В данной транзакции уже происходили ошибки!» (в среде SQL Server известная как The transaction is in an uncommittable state) возникает в строго определенном сценарии. Рассмотрим его по шагам:
НачатьТранзакцию() или неявно при записи объекта).Отказ = Истина в обработчике события (например, ПередЗаписью).Попытка ... Исключение, но транзакция не была отменена немедленно, она переходит в состояние «битой» (нефиксируемой).Особенно часто это случается при использовании вложенных транзакций. Если внутренняя транзакция пометила себя как несостоявшуюся, внешняя транзакция больше не может быть зафиксирована. Попытка вызвать ЗафиксироватьТранзакцию() для внешней транзакции вернет нам ту самую ошибку.
Это самый первый и простой метод, который стоит попробовать. Наша задача — поймать ошибку в момент ее первичного возникновения, а не тогда, когда платформа сообщает о «битой» транзакции. Проанализируем, как это сделать:
Попытка.Когда отладчик прервет выполнение, посмотрите на стек вызовов. Вы окажетесь ровно в том месте, где возник первый сбой (например, не заполнено обязательное поле или сработала проверка уникальности номера). Объект, который находится в памяти в этот момент, и есть наш «виновник».
Бывают случаи, когда ошибку невозможно воспроизвести под отладкой (например, она возникает только на сервере в регламентном задании). Тогда на помощь приходит Технологический журнал. Мы можем настроить сбор событий EXCP, которые регистрируют все исключительные ситуации.
Для этого создадим файл logcfg.xml в каталоге C:\Program Files\1cv8\conf (для конкретной версии платформы) со следующим содержанием:
<config xmlns="http://v8.1c.ru/7.4/config">
<log location="C:\LOGS\TRANS_ERRORS" history="2">
<event>
<eq property="Name" value="EXCP"/>
</event>
<event>
<eq property="Name" value="DBV8DBENG"/>
</event>
<property name="All"/>
</log>
</config>
Что мы ищем в логах? Проанализируем файлы журналов, которые появятся в папке C:\LOGS\TRANS_ERRORS. Нам нужно найти первое событие EXCP по времени в рамках вашего сеанса. В описании события часто можно увидеть текст ошибки СУБД или сообщение от самой платформы 1С. Например, там может быть указано имя таблицы (например, _Reference150), что позволит через ПолучитьСтруктуруХраненияБазыДанных() вычислить конкретный справочник.
Если вы используете обработку УниверсальныйОбменДаннымиXML, то проблема часто скрыта в обработчиках «ПередЗаписью» или «ПриЗагрузке». Чтобы увидеть реальное описание ошибки, а не системное сообщение о транзакции, выполните следующие действия:
Это позволит платформе игнорировать общую ошибку транзакции и показать конкретный текст, например: «Поле 'Склад' не заполнено». Вы сразу поймете, какой объект (документ или справочник) пришел с неполными данными.
Если вы сами пишете код загрузки, проанализируем правильный подход к обработке ошибок. Рассмотрим пример кода, который часто приводит к проблеме, и как его исправить. Рассмотрим плохой пример:
НачатьТранзакцию();
Попытка
Объект.Записать();
Исключение
// ОШИБКА: Мы просто вывели сообщение, но транзакция осталась открытой и "битой"
Сообщить("Не удалось записать объект: " + ОписаниеОшибки());
КонецПопытки;
// Следующее действие вызовет ошибку "В данной транзакции уже происходили ошибки"
ДругойОбъект.Записать();
ЗафиксироватьТранзакцию();
А теперь изучим, как этот код должен выглядеть правильно:
НачатьТранзакцию();
Попытка
Объект.Записать();
Исключение
// ПРАВИЛЬНО: Сначала отменяем транзакцию, чтобы освободить ресурсы и сбросить статус ошибки
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
Сообщить("Запись прервана: " + ОписаниеОшибки());
Возврат; // Прекращаем выполнение, так как транзакция уже невалидна
КонецПопытки;
ЗафиксироватьТранзакцию();
Часто ошибки в транзакциях возникают из-за избыточных проверок в модулях объектов (проверки остатков, заполнение реквизитов, подписки на события). Если ваша задача — просто загрузить данные из другой системы, проанализируем использование свойства ОбменДанными.Загрузка.
Установите перед записью объекта следующий код:
Объект.ОбменДанными.Загрузка = Истина;
Это отключит большинство прикладных проверок и обработчиков, что позволит объекту записаться даже с «кривыми» данными. После этого вы сможете найти эти объекты простым запросом и исправить их вручную или программно.
Если у вас есть доступ к SQL серверу, мы можем запустить SQL Profiler. Это позволит увидеть, на какой именно инструкции INSERT или UPDATE сервер вернул ошибку. Проанализируйте события Exception и User Error Message. В тексте события часто будет указан идентификатор записи или нарушения уникальности индекса, что является прямой уликой против конкретного объекта базы данных.
Подведем итог: чтобы найти «виновного» объекта, не нужно смотреть на само сообщение об ошибке транзакции. Нужно искать самое первое исключение, произошедшее в рамках процесса. Используйте для этого «Остановку по ошибке» в конфигураторе или Технологический журнал, и вы быстро локализуете проблему.