Ситуация, когда платформа 1С сообщает: «В данной транзакции уже происходили ошибки!» на совершенно корректных строках кода (например, при вызове НайтиПоКоду() или чтении значения константы), является одной из самых частых и вводящих в заблуждение проблем. Разберем механику этой ошибки, выясним истинные причины её возникновения и рассмотрим надежные методы поиска и устранения сбоя.
Проанализируем, как работает транзакционный механизм платформы 1С:Предприятие при взаимодействии с базой данных:
setRollbackOnly). С этого момента ядро СУБД блокирует абсолютно любые дальнейшие операции с базой данных — не только запись, но и обычное чтение.Попытка ... Исключение, и разработчик в блоке Исключение проигнорировал ошибку (не отменил транзакцию и не выбросил исключение наружу), выполнение алгоритма продолжается дальше в контексте уже сломанной транзакции.Константы.РежимЗавершенияРаботыПользователей.Получить() или метод НайтиПоКоду()) мгновенно прерывается платформой с текстом «В данной транзакции уже происходили ошибки!».Таким образом, строка, указанная в системном сообщении об ошибке, является лишь местом первого чтения данных после катастрофы, а не её источником.
Часто разработчики предполагают, что если в коде нет явного вызова НачатьТранзакцию(), то транзакции нет. Это заблуждение. При старте сеанса пользователя конфигурация и ядро платформы неявно инициируют транзакции для целого ряда операций:
Если в одной из этих процедур возникает ошибка, перехваченная пустой конструкцией Попытка ... Исключение, интерфейс продолжает открываться, но первая же проверка константы приводит к падению сеанса.
Рассмотрим порядок действий для обнаружения скрытого сбоя встроенными средствами разработки:
Попытка, где произошла первая ошибка работы с СУБД.Если ошибка воспроизводится плавающим образом только на рабочем сервере под нагрузкой, применим настройку Технологического журнала. Создадим в каталоге конфигурации технологического журнала (по умолчанию для 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>
Проанализируем события в логе:
SDBL со свойством Func="setRollbackOnly" фиксирует точный момент поломки транзакции. В его параметрах будет указан контекст вызова (стек) и строка кода.EXCP, произошедшее в том же потоке (OSThread) и сеансе (t:connectID, SessionID) за миллисекунды до этого, покажет точное сообщение СУБД (таймаут блокировки, дедлок или нарушение ограничений целостности).При использовании платформы 1С 8.2 в связке с MS SQL Server 2014 важно учитывать системные ограничения:
SQL Server 2012 (110) или SQL Server 2008 (100).-T9481 (принудительное использование классической модели оценки кардинальности) и -T4199.Падение транзакций часто связано с аварийным завершением рабочих процессов rphost. В платформе 8.2 на каждый рабочий процесс выделяется пул соединений. При резком увеличении числа пользователей соединения перегружаются, а при сбое одного запроса может пострадать весь пул.
Для стабилизации выполним следующие шаги:
rphost исходя из нагрузки (например, 1 рабочий процесс на каждые 25–35 активных пользователей или на 2–3 ядра процессора).Посмотрим на пример некорректного кода, порождающего рассматриваемую ошибку:
НачатьТранзакцию();
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Ошибка перехвачена, транзакция осталась открытой и сломанной!
ЗаписьЖурналаРегистрации("Ошибка записи", УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
// Попытка чтения данных в сломанной транзакции вызовет аварийный сбой:
Подразделение = Справочники.Подразделения.НайтиПоКоду("00001");
ЗафиксироватьТранзакцию();
Перепишем данный алгоритм по стандартам безопасной работы с транзакциями платформы 1С:
НачатьТранзакцию();
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Подразделение = Справочники.Подразделения.НайтиПоКоду("00001");
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Ошибка проведения", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Главное правило: если внутри транзакции используется блок Попытка ... Исключение, в секции Исключение необходимо обязательно вызвать ОтменитьТранзакцию() либо пробросить исключение командой ВызватьИсключение;, не допуская дальнейшего чтения или записи данных в поврежденном транзакционном контексте.