Работа с транзакциями в 1С:Предприятие 8 — это фундамент целостности данных. Однако программисты часто сталкиваются с крайне коварной ошибкой: «В данной транзакции уже происходили ошибки». Эта ситуация сбивает с толку, поскольку она возникает не в момент самой первичной ошибки, а чуть позже, маскируя истинную причину проблемы. В данной статье мы подробно разберем, почему это происходит, как платформа 1С взаимодействует с СУБД и каков правильный алгоритм написания кода для создания цепочек документов.
Рассмотрим стандартную ситуацию: нам необходимо создать «Документ2» и «Документ3» сразу после записи «Документа1». Для этого мы используем обработчик ПослеЗаписиНаСервере и оборачиваем создание подчиненных документов в явную транзакцию. Проанализируем, что происходит на уровне системы, когда что-то идет не так.
Когда внутри блока НачатьТранзакцию() случается программный сбой (например, не пройдена проверка заполнения или сработал отказ в ПередЗаписью), СУБД (MS SQL Server, PostgreSQL) помечает текущую транзакцию как «обреченную» (doomed). С этого момента и до выполнения команды ОтменитьТранзакцию() база данных заблокирована для любых операций чтения или записи в рамках этого сеанса. Любая попытка обратиться к таблицам БД приведет к выбросу исключения «В данной транзакции уже происходили ошибки».
Выясним главную причину, по которой автор темы на форуме не мог корректно обработать ошибку. Проблема крылась в строке кода, которая должна была информировать пользователя о сбое. Посмотрим на этот фрагмент:
Иначе
ОбщегоНазначения.СообщитьПользователю(СтрШаблон("%1", Элемент.СсылкаНаОснование));
ОтменитьТранзакцию();
КонецЕсли;
Казалось бы, мы просто хотим вывести сообщение. Но здесь кроется нюанс: обращение к Элемент.СсылкаНаОснование в методе СтрШаблон или при обычном выводе сообщения заставляет платформу получить Представление объекта. Если ссылка не находится в кэше, 1С выполняет неявный запрос SELECT к базе данных, чтобы получить наименование или номер документа. Поскольку транзакция уже «сломана» предыдущей ошибкой при записи, этот неявный запрос падает, и мы видим сообщение о «происходивших ошибках» вместо текста реальной проблемы.
Самый простой и надежный способ избежать этой ошибки — изменить последовательность действий. Мы должны сначала высвободить транзакцию, и только потом выполнять любые операции, требующие обращения к данным. Проанализируем правильный шаблон обработки:
НачатьТранзакцию();
Попытка
// Логика создания документов
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
// ВАЖНО: Сначала отменяем транзакцию, чтобы СУБД позволила делать новые запросы
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
// Теперь можно безопасно обращаться к ссылкам и выводить сообщения
СтрокаОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ОбщегоНазначения.СообщитьПользователю("Ошибка создания: " + СтрокаОшибки);
КонецПопытки;
Чтобы не зависеть от состояния транзакции при формировании сообщений об ошибках, мы можем заранее подготовить все текстовые данные. Рассмотрим стратегию «Подготовил — Проверил — Записал». Если мы сохраним строковые представления ключевых объектов в локальные переменные до начала опасных операций, то в блоке Исключение нам не понадобится обращаться к базе данных.
Пример кода с предварительной подготовкой:
Для Каждого СтрокаДанных Из Коллекция Цикл
ПредставлениеОснования = Строка(СтрокаДанных.СсылкаНаОснование); // Данные в кэше
НачатьТранзакцию();
Попытка
// Создание Документа2
// ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить("Не удалось обработать: " + ПредставлениеОснования);
КонецПопытки;
КонецЦикла;
Если цепочка создаваемых документов длинная (например, создание 5–10 связанных сущностей), лучше всего вынести этот процесс в фоновое задание. Это не только улучшит отзывчивость интерфейса для пользователя, но и полностью изолирует транзакцию записи основного документа от транзакций создания подчиненных.
Проанализируем преимущества этого подхода:
Многие разработчики совершают ошибку при использовании отладчика. Когда выполнение останавливается на точке останова внутри блока Исключение, попытка посмотреть значение реквизитов объекта в «Табло» или через «Вычислить выражение» сама по себе вызывает обращение к БД. В этот момент отладчик выдаст ту самую ошибку «В данной транзакции уже происходили ошибки».
Важный совет: Не доверяйте значениям переменных в Табло, если транзакция еще не была отменена. Чтобы увидеть реальное состояние данных, сначала выполните шаг (F10) через команду ОтменитьТранзакцию(), и только после этого анализируйте состояние объектов.
Для стабильной работы системы при записи связанных объектов мы рекомендуем придерживаться следующих правил:
ТранзакцияАктивна() перед вызовом ОтменитьТранзакцию().ПослеЗаписиНаСервере) будьте осторожны с явными транзакциями: программная отмена транзакции может привести к рассинхронизации состояния формы и данных в БД.Исключение до того, как транзакция будет официально откачена.