Почему возникает ошибка «В данной транзакции уже происходили ошибки» и как её исправить?

Программист 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

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

Механизм ошибки: почему система указывает на невиновный участок кода

Проанализируем, как работает транзакционный механизм платформы 1С:Предприятие при взаимодействии с базой данных:

  1. Внутри открытой транзакции (явной или неявной) происходит сбой обращения к СУБД: таймаут на блокировке, взаимная блокировка (deadlock), нарушение уникальности индекса или любая другая системная ошибка СУБД.
  2. Платформа переводит текущую транзакцию во внутреннее состояние «только откат» (флаг setRollbackOnly). С этого момента ядро СУБД блокирует абсолютно любые дальнейшие операции с базой данных — не только запись, но и обычное чтение.
  3. Если исходный сбой произошел внутри конструкции Попытка ... Исключение, и разработчик в блоке Исключение проигнорировал ошибку (не отменил транзакцию и не выбросил исключение наружу), выполнение алгоритма продолжается дальше в контексте уже сломанной транзакции.
  4. Первое же последующее обращение к СУБД (например, Константы.РежимЗавершенияРаботыПользователей.Получить() или метод НайтиПоКоду()) мгновенно прерывается платформой с текстом «В данной транзакции уже происходили ошибки!».

Таким образом, строка, указанная в системном сообщении об ошибке, является лишь местом первого чтения данных после катастрофы, а не её источником.

Неявные транзакции при старте системы

Часто разработчики предполагают, что если в коде нет явного вызова НачатьТранзакцию(), то транзакции нет. Это заблуждение. При старте сеанса пользователя конфигурация и ядро платформы неявно инициируют транзакции для целого ряда операций:

  1. Инициализация параметров сеанса и проверка прав доступа пользователей.
  2. Запись в регистры сведений (история работы пользователя, регистрация активных сессий).
  3. Обновление настроек компоновки данных и сохранение пользовательских настроек форм при открытии.
  4. Автоматический запуск регламентных и фоновых процедур.

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

Локализация ошибки через отладчик в Конфигураторе

Рассмотрим порядок действий для обнаружения скрытого сбоя встроенными средствами разработки:

  1. Откроем Конфигуратор и перейдем в меню Отладка -> Настройка остановки по ошибке.
  2. Установим флаг Останавливаться по ошибке и снимем галочку Останавливаться только по ошибкам, не обрабатываемым в коде (или активируем остановку по всем исключениям, включая обрабатываемые).
  3. Запустим сеанс «Предприятия» под учетной записью проблемного пользователя в режиме отладки.
  4. При возникновении исключительной ситуации отладчик прервет выполнение программы на истинном источнике сбоя — внутри блока Попытка, где произошла первая ошибка работы с СУБД.

Точный поиск первопричины через Технологический журнал

Если ошибка воспроизводится плавающим образом только на рабочем сервере под нагрузкой, применим настройку Технологического журнала. Создадим в каталоге конфигурации технологического журнала (по умолчанию для 64-битного сервера: C:\Program Files\1cv82\conf\logcfg.xml) следующий конфигурационный файл:


<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\Logs\1C_Rollback" history="24">
        <event>
            <eq property="name" value="SDBL"/>
            <eq property="Func" value="setRollbackOnly"/>
        </event>
        <event>
            <eq property="name" value="EXCP"/>
        </event>
        <property name="all"/>
    </log>
</config>

Проанализируем события в логе:

  1. Событие SDBL со свойством Func="setRollbackOnly" фиксирует точный момент поломки транзакции. В его параметрах будет указан контекст вызова (стек) и строка кода.
  2. Событие EXCP, произошедшее в том же потоке (OSThread) и сеансе (t:connectID, SessionID) за миллисекунды до этого, покажет точное сообщение СУБД (таймаут блокировки, дедлок или нарушение ограничений целостности).

Совместимость платформы 8.2 и MS SQL Server 2014

При использовании платформы 1С 8.2 в связке с MS SQL Server 2014 важно учитывать системные ограничения:

  1. Платформа 8.2 не была оптимизирована под механизмы оптимизатора запросов MS SQL 2014 (новый Cardinality Estimator). Это часто приводит к неоптимальным планам выполнения запросов, длительным блокировкам таблиц и, как следствие, таймаутам в транзакциях.
  2. Для устранения проблем совместимости перейдем в свойства базы данных в SQL Server Management Studio (SSMS), откроем вкладку Options и установим Compatibility level в значение SQL Server 2012 (110) или SQL Server 2008 (100).
  3. Дополнительно в параметрах запуска службы SQL Server включим флаги трассировки -T9481 (принудительное использование классической модели оценки кардинальности) и -T4199.

Устранение ошибки «Рабочий процесс не найден» и настройка кластера

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

Для стабилизации выполним следующие шаги:

  1. В консоли администрирования кластера 1С увеличим количество рабочих процессов rphost исходя из нагрузки (например, 1 рабочий процесс на каждые 25–35 активных пользователей или на 2–3 ядра процессора).
  2. На сетевых картах серверов 1С и СУБД отключим аппаратные разгрузки (TCP Chimney Offload, Large Send Offload), так как в операционных системах Windows Server 2008 они могут приводить к самопроизвольному разрыву постоянных соединений между сервером 1С и SQL Server.

Стандарты написания кода: как правильно работать с транзакциями

Посмотрим на пример некорректного кода, порождающего рассматриваемую ошибку:


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

// Попытка чтения данных в сломанной транзакции вызовет аварийный сбой:
Подразделение = Справочники.Подразделения.НайтиПоКоду("00001");
ЗафиксироватьТранзакцию();

Перепишем данный алгоритм по стандартам безопасной работы с транзакциями платформы 1С:


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

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

← На главную