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

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

Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с крайне коварной ошибкой: «В данной транзакции уже происходили ошибки!». Коварство этой проблемы заключается в том, что стек вызова указывает на абсолютно безобидный участок кода — например, на чтение константы, получение ссылки или простой запрос. При этом реальная причина сбоя произошла гораздо раньше и в совершенно другом месте.

В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм «поломанной транзакции», почему стандартный отладчик часто молчит и как пошагово найти истинного виновника проблемы.

Суть проблемы: механизм «поломанной транзакции»

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

Проанализируем классический сценарий, приводящий к сбою:

  1. Платформа 1С открывает транзакцию (например, неявно при начале проведения документа док.Записать(РежимЗаписиДокумента.Проведение)).
  2. Внутри транзакции выполняется какой-то код, который приводит к ошибке СУБД. Это может быть нарушение уникальности индекса, конфликт блокировок, превышение времени ожидания или ошибка проведения по регистру.
  3. Этот сбой происходит внутри программного блока Попытка ... Исключение.
  4. Конструкция Попытка успешно «гасит» ошибку в коде 1С, предотвращая аварийное завершение работы программы в этот конкретный момент.
  5. Но на уровне СУБД транзакция уже помечена как «обреченная» (doomed transaction) или сломанная. База данных больше не примет ни одной команды в рамках этой транзакции, кроме команды полного отката (ROLLBACK).
  6. Код 1С продолжает выполняться дальше (ведь ошибка была перехвачена в Исключении).
  7. При первой же попытке любого последующего обращения к базе данных внутри этой же транзакции (например, при чтении константы Константы.ИспользоватьВерсионированиеОбъектов.Получить() или записи другого объекта) СУБД наотрез отказывается работать и выбрасывает платформенное исключение: «В данной транзакции уже происходили ошибки!».

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

Почему в режиме Предприятия всё проводится без проблем?

Часто разработчики недоумевают: почему при ручном проведении того же самого документа в интерактивном режиме или при запуске внешней обработки на сервере никаких ошибок не возникает? Выделим две основные причины:

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

Во-вторых, различие в правах и контексте выполнения. Регламентные задания выполняются от имени специального пользователя (как правило, с полными правами) и в фоновом режиме на сервере, где могут срабатывать специфические подписки на события или проверки, неактивные при обычном сеансе.

Почему не срабатывает «Остановка по ошибке» в отладчике?

Если вы попытаетесь отловить эту ошибку стандартным запуском отладки, то столкнетесь с тем, что конфигуратор упорно игнорирует первоначальное место сбоя. Выясним, почему это происходит:

  1. Отсутствие подключения к фоновым заданиям. Регламентные задания выполняются на сервере в фоновом режиме. По умолчанию отладчик конфигуратора к ним не подключается.
  2. Пропуск обрабатываемых исключений. Так как первоначальная ошибка завернута в блок Попытка ... Исключение, отладчик по умолчанию считает её «обработанной» программно и не останавливает на ней выполнение. Остановка происходит только на финальной ошибке обращения к поломанной транзакции, которая уже неинформативна.

Пошаговое руководство по локализации истинной ошибки

Рассмотрим подробнее три эффективных метода, которые помогут вам быстро найти место возникновения первичного сбоя.

Способ 1. Настройка отладчика для фоновых заданий

Для того чтобы поймать ошибку непосредственно в момент её возникновения в регламентном задании, выполните следующие шаги:

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

Способ 2. Анализ Технологического Журнала (ТЖ)

Если база данных работает в клиент-серверном режиме под высокой нагрузкой и отладка «на лету» затруднительна, лучшим инструментом становится Технологический Журнал 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>

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

Способ 3. Временное отключение блоков перехвата ошибок

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

Правильные стандарты написания транзакционного кода

Для того чтобы в будущем не допускать подобных ситуаций в собственной разработке, важно соблюдать железное правило стандартов разработки 1С:

Никогда не используйте конструкцию Попытка-Исключение внутри транзакции без последующего немедленного её отката!

Посмотрим на пример неправильного кода, который гарантированно приведет к ошибке поломанной транзакции при возникновении любого сбоя записи:


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

А теперь проанализируем правильный шаблон реализации групповой обработки. Если один из документов пакета не провелся, мы обязаны сразу же отменить всю транзакцию целиком:


НачатьТранзакцию();
Успешно = Истина;

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

Если Успешно Тогда
    ЗафиксироватьТранзакцию();
Иначе
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение "Групповое проведение отменено из-за ошибки в одном из документов!";
КонецЕсли;

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

← На главную