Как исправить ошибку «В данной транзакции уже происходили ошибки» в 1С:Предприятие

Программист 1С v8.3 (Обычные формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

Каждый разработчик 1С рано или поздно сталкивается с системным сообщением: «В данной транзакции уже происходили ошибки». В профессиональной среде эту проблему часто называют ошибкой «сломанной», «отравленной» или «испорченной» транзакции (состояние Rollback-Only). Данный сбой ставит в тупик многих специалистов, так как платформа указывает на строку кода, которая сама по себе абсолютно корректна. В этой статье мы подробно разберем природу этого явления, выясним, почему замена даты документа временно маскирует проблему, и научимся находить истинную причину сбоя с помощью отладчика и технологического журнала.

Физическая и архитектурная суть проблемы

Для того чтобы понять, почему возникает эта ошибка, нам необходимо обратиться к архитектуре взаимодействия платформы «1С:Предприятие» и систем управления базами данных (СУБД), такими как MS SQL Server или PostgreSQL. Транзакция — это неделимая последовательность операций над базой данных. Она должна либо выполниться полностью (коммит), либо полностью отмениться (откат), сохраняя целостность данных.

Когда транзакция запущена (неважно, явно через код НачатьТранзакцию() или неявно при записи/проведении документа), СУБД берет под жесткий контроль все дисковые и блокировочные операции. Если внутри транзакции происходит любая критическая ошибка СУБД — например, конфликт блокировок, нарушение уникальности индексов, запись в несуществующую таблицу или попытка проведения документа по регистрам с ошибкой контроля остатков — СУБД мгновенно помечает текущую транзакцию как нежизнеспособную (Rollback-Only).

Платформа 1С транслирует эту ошибку в код. Если разработчик обернул опасный участок кода в конструкцию Попытка...Исключение, то платформа перехватывает первичный сбой, позволяя коду выполняться дальше. Однако СУБД уже приняла решение: данная транзакция никогда не будет зафиксирована. Любое последующее обращение к базе данных (даже простой запрос на чтение или получение представления ссылки) в рамках этой транзакции пресекается СУБД на корню. Платформа 1С реагирует на это повторное обращение выводом сообщения: «В данной транзакции уже происходили ошибки».

Таким образом, строка кода, на которой спотыкается программа, невиновна. Настоящая ошибка произошла раньше, была «замята» блоком Попытка...Исключение, но бесследно для СУБД не прошла.

Разбор ситуации автора: почему помогла смена даты?

Рассмотрим фрагмент кода, который привел автор темы на форуме:


Попытка               
    РезультатЗапроса = Запрос.Выполнить();
Исключение
    ТекстСообщения = "Не удалось выполнить запрос по остаткам";
    Возврат Ложь;
КонецПопытки;

Автор пишет, что документ «МЗ_ВнутреннееПеремещение» при проведении вываливается в исключение именно на строке РезультатЗапроса = Запрос.Выполнить(). Однако при переносе даты документа на другую (например, с 06.10.18 на 10.11.18) документ проводится без нареканий.

Проанализируем эту ситуацию. Смена даты меняет контекст данных, к которым обращается документ при проведении. На дату 06.10.18 в базе данных происходило некое событие, порождавшее первичную ошибку. Это могло быть:

Когда первичный сбой происходил, транзакция помечалась как «сломанная». Далее код управления доходил до выполнения запроса остатков. Из-за поврежденного статуса транзакции метод Запрос.Выполнить() завершался аварийно. Конструкция Попытка перехватывала эту ошибку, но исправить ситуацию уже не могла. При смене даты на ноябрь условия для первичного сбоя исчезли, транзакция осталась «чистой», и запрос выполнился штатно.

Как найти истинную причину ошибки: пошаговое руководство

Поскольку сама ошибка указывает лишь на следствие, нам необходимо раскопать первопричину. Сделать это можно тремя надежными способами.

Способ 1. Настройка отладчика конфигуратора (самый быстрый путь)

Обычный запуск отладки не останавливает выполнение на ошибках, которые обернуты в блок Попытка...Исключение. Нам нужно заставить конфигуратор реагировать на абсолютно все исключительные ситуации, даже если они обрабатываются кодом.

Выполним следующие шаги:

  1. Откроем базу данных в режиме Конфигуратор.
  2. В главном меню перейдем в раздел Отладка -> Остановка по ошибке...
  3. В открывшемся окне установим флажок Останавливаться по ошибке.
  4. Обязательно установим флажок Исключение (или выберем пункт «Все ошибки» в зависимости от версии платформы).
  5. Поле «Имя ошибки» оставим пустым, чтобы ловить все типы системных сбоев.
  6. Запустим сеанс «1С:Предприятие» под отладкой и воспроизведем проведение документа за проблемную дату (06.10.18).

Теперь при проведении документа отладчик остановит исполнение кода не на строке РезультатЗапроса = Запрос.Выполнить(), а гораздо раньше — непосредственно в момент возникновения первого сбоя СУБД. Это может быть подписка на событие, модуль набора записей регистра или общий модуль проведения. Мы увидим реальный текст системной ошибки (например, деление на ноль, нарушение уникальности ключа или блокировка).

Способ 2. Анализ с помощью Технологического журнала (для серверных баз)

Если ошибка воспроизводится только на рабочем сервере под нагрузкой и подключить отладчик напрямую невозможно, на помощь приходит Технологический журнал (ТЖ) 1С. Мы можем настроить его так, чтобы он фиксировал все исключительные ситуации.

Для этого в каталоге настроек ТЖ (обычно это C:\Program Files\1cv8\conf\ на сервере) создадим или отредактируем файл logcfg.xml следующим образом:


<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\v8logs\exceptions" history="2">
        <event>
            <eq property="Name" value="EXCP"/>
        </event>
        <property name="all"/>
    </log>
</config>

После активации этой настройки платформа начнет записывать все исключения (событие EXCP) в указанную папку. Нам останется лишь воспроизвести ошибку, открыть лог-файл и найти записи, предшествующие сообщению «В данной транзакции уже происходили ошибки». Там будет содержаться полный стек вызовов и детальное описание первоначального исключения СУБД.

Способ 3. Изучение Журнала регистрации

Иногда разработчики типовых конфигураций (например, БГУ или БП) предусматривают запись первичных ошибок в Журнал регистрации внутри блока Исключение, прежде чем вернуть управление. Проанализируем Журнал регистрации, установив фильтр по нашему пользователю и ограничив интервал времени секундами проведения документа. Возможно, там уже записано готовое сообщение об ошибке проведения по конкретному регистру.

Стандарты разработки 1С по предотвращению «отравления» транзакций

Чтобы не плодить подобные трудночитаемые ошибки в собственном коде, необходимо строго следовать стандартам разработки, которые рекомендует фирма «1С».

Правило 1. Запрет подавления транзакционных ошибок.
Если вы используете блок Попытка...Исключение внутри транзакции (например, в обработчиках ПередЗаписью, ПриЗаписи, ОбработкаПроведения) и в блоке Попытка происходит любое обращение к базе данных, вы обязаны гарантировать прерывание транзакции в случае неудачи. Заминать ошибку и возвращать Ложь нельзя!

Посмотрим на пример неправильного кода:


// НЕПРАВИЛЬНО! Так делать нельзя!
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Попытка
        НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
        // ... заполнение и запись набора
        НаборЗаписей.Записать(); 
    Исключение
        // Ошибка перехвачена, но транзакция уже сломана СУБД!
        ЗаписатьВЛог("Не удалось записать цены"); 
        // Код продолжает выполнение, провоцируя последующий крах транзакции
    КонецПопытки;
КонецПроцедуры

Правильный вариант перехвата ошибок внутри транзакции всегда должен содержать оператор ВызватьИсключение, чтобы платформа корректно инициировала откат всей транзакции:


// ПРАВИЛЬНО!
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Попытка
        НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
        // ... заполнение и запись набора
        НаборЗаписей.Записать(); 
    Исключение
        // Фиксируем информацию для администратора
        ЗаписатьВЛог("Критическая ошибка записи цен: " + ОписаниеОшибки()); 
        // Прерываем транзакцию и показываем пользователю исходную ошибку
        ВызватьИсключение; 
    КонецПопытки;
КонецПроцедуры

Правило 2. Валидация вместо попыток записи.
Вместо того чтобы пытаться записать объект в базу данных и перехватывать ошибку в случае неудачи, мы должны предварительно проверять все условия программно. Например, вместо попытки проведения документа по регистру с надеждой отловить ошибку нехватки остатков через Попытка, следует предварительно выполнить запрос к остаткам и убедиться, что ресурсов достаточно.

Резюме

Выяснив причину ошибки «В данной транзакции уже происходили ошибки», мы понимаем, что она является лишь защитным механизмом СУБД, сигнализирующим о попытке работать со сломанной транзакцией. Попытка проигнорировать первичный сбой с помощью конструкции Попытка...Исключение приводит к потере контекста ошибки. Использование правильной настройки отладчика с остановкой по обрабатываемым исключениям позволяет за пару минут локализовать исходное место сбоя и исправить логику работы программы.

← На главную