Почему при отмене транзакции в 1С не откатываются изменения формы и значения переменных?

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

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

Суть проблемы: ожидание и реальность

Рассмотрим ситуацию, с которой столкнулся автор вопроса. Логика кода строится на предположении, что блок транзакции защищает абсолютно все действия внутри него.

Взглянем на пример кода, который вызывает недоумение:


НачатьТранзакцию();
Попытка
    // Меняем интерфейс
    Элементы.МойРеквизит.Видимость = Истина;
    
    // Искусственно вызываем ошибку
    а = 1/0; 
    
    ЗафиксироватьТранзакцию();
Исключение
    // Откатываем транзакцию
    ОтменитьТранзакцию();
КонецПопытки;

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

Разделение зон ответственности: База данных vs Оперативная память

Чтобы понять поведение системы, нам нужно четко разделить два понятия: База данных и Оперативная память приложения.

  1. Транзакция СУБД (База данных). Механизм транзакций в 1С управляет исключительно целостностью данных, хранящихся на жестком диске в таблицах базы данных. Команды НачатьТранзакцию, ЗафиксироватьТранзакцию и ОтменитьТранзакцию — это команды, передаваемые серверу СУБД (SQL Server, PostgreSQL и т.д.). Они гарантируют, что набор записей в таблицы либо применится целиком, либо не применится вовсе.
  2. Оперативная память (Интерфейс и Переменные). Свойства элементов формы (такие как Видимость, Доступность, Заголовок), значения локальных переменных модуля, состояние открытых окон — все это живет в оперативной памяти клиентского приложения или сервера 1С. Менеджер транзакций базы данных не имеет доступа к этой области памяти и не отслеживает ее состояние.

Когда мы пишем Элементы.МойРеквизит.Видимость = Истина, это действие выполняется в памяти моментально. Система не запоминает "старое" состояние для целей транзакции. Поэтому, когда вызывается ОтменитьТранзакцию(), СУБД отменяет изменения в таблицах, но память программы остается в том состоянии, в которое мы ее привели своими действиями.

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

Правильное решение: Управление через флаги успеха

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

Давайте перепишем код, используя переменную-флаг (например, Успех):


Успех = Ложь;

НачатьТранзакцию();
Попытка
    // Выполняем опасные действия с данными
    // ... запись справочников, документов ...
    
    // Если дошли до этой строки, значит ошибок не было
    ЗафиксироватьТранзакцию();
    Успех = Истина;
Исключение
    ОтменитьТранзакцию();
    // Обработка ошибки (запись в журнал, сообщение пользователю)
    Успех = Ложь;
КонецПопытки;

// Меняем интерфейс только если транзакция прошла успешно
Если Успех Тогда
    Элементы.МойРеквизит.Видимость = Истина;
КонецЕсли;

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

Стандарты написания кода транзакций

В ходе обсуждения темы был затронут важный вопрос: где правильно размещать вызов НачатьТранзакцию() — внутри блока Попытка или перед ним? Давайте обратимся к стандартам разработки 1С.

Рекомендуемый шаблон выглядит так:


НачатьТранзакцию();
Попытка
    // Чтение или запись данных
    ДокументОбъект.Записать();
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    // Дополнительные действия по обработке исключения
    ВызватьИсключение; // Или запись в лог
КонецПопытки;

Почему НачатьТранзакцию() пишут ДО попытки?

Это делается для корректной обработки ошибок самого старта транзакции. Если по какой-то причине (например, потеря связи с SQL-сервером) транзакцию начать невозможно, то код упадет с ошибкой еще до входа в блок защиты. Если бы мы поместили старт внутрь, то при ошибке старта мы бы попали в Исключение, где стоит ОтменитьТранзакцию(). Но так как транзакция не началась, попытка её отменить вызвала бы вторую ошибку ("Нет активной транзакции"), которая могла бы запутать диагностику.

Безопасная отмена транзакции

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

Модифицируем блок Исключение:


Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    
    // Вывод информации об ошибке
    Сообщить(ОписаниеОшибки());
КонецПопытки;

Метод ТранзакцияАктивна() возвращает Истина, если в текущем сеансе открыта транзакция, которую можно отменить или зафиксировать.

Нюансы вложенных транзакций

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

  1. Счетчик ссылок. Реальная транзакция СУБД начинается только при первом вызове НачатьТранзакцию(). Все последующие вызовы внутри (вложенные) просто увеличивают внутренний счетчик транзакций (+1).
  2. Фиксация. Команда ЗафиксироватьТранзакцию() уменьшает счетчик (-1). Данные реально записываются в БД (COMMIT) только когда счетчик достигает нуля.
  3. Отмена. Команда ОтменитьТранзакцию() работает иначе. Она мгновенно отменяет всю транзакцию целиком, независимо от уровня вложенности, и сбрасывает счетчик в ноль.

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

Выводы и рекомендации

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

← На главную