Транзакции в 1С — это группа изменений в базе данных, которая либо фиксируется целиком, либо целиком откатывается. Ключевые факты, на которых чаще всего ошибаются: настоящих вложенных транзакций в 1С нет — повторный НачатьТранзакцию() только увеличивает счётчик; ОтменитьТранзакцию() откатывает только данные в базе, но не переменные и не объекты в памяти; а запись объекта и все её обработчики уже идут в транзакции, открытой платформой.
Разберу четыре коротких примера кода, которые хорошо проверяют понимание механизма, и покажу правильный шаблон работы с транзакцией.
Что нужно знать о транзакциях в 1С
- Атомарность. Все изменения внутри транзакции применяются вместе или не применяются вовсе.
- Неявные транзакции. Платформа сама открывает транзакцию при
Записать(), проведении, удалении объекта. ОбработчикиПередЗаписью,ПриЗаписи,ОбработкаПроведенияи подписки на эти события работают внутри неё. - Счётчик вложенности.
НачатьТранзакцию()внутри активной транзакции не создаёт новую — увеличивает счётчик. Реальная фиксация — при выходе на нулевой уровень. - Блокировки. Изменённые данные остаются заблокированными до конца транзакции. Длинная транзакция — это ожидания и взаимоблокировки у других пользователей.
Проверить, открыта ли транзакция, можно функцией ТранзакцияАктивна().
Пример 1. Явная транзакция внутри записи объекта
Процедура ПередЗаписью(Отказ)
Если Согласовано Тогда
ОбработатьПодчиненныеОбъекты();
КонецЕсли;
КонецПроцедуры
Процедура ОбработатьПодчиненныеОбъекты()
НачатьТранзакцию();
Попытка
Для Каждого Ссылка Из ПолучитьЗависимыеОбъекты() Цикл
ЗависимыйОбъект = Ссылка.ПолучитьОбъект();
ЗависимыйОбъект.Согласовано = Истина;
ЗависимыйОбъект.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
КонецПроцедурыНа первый взгляд всё правильно: начали, зафиксировали, при ошибке отменили. Проблем две.
Транзакция уже открыта. ПередЗаписью выполняется внутри транзакции записи объекта. Наш НачатьТранзакцию() лишь поднимет счётчик до двух — никакой отдельной «транзакции для подчинённых» не появится. Явная транзакция здесь бесполезна.
Ошибка проглатывается. Если запись подчинённого объекта упадёт, ОтменитьТранзакцию() пометит всю транзакцию как неуспешную, а исключение дальше не пойдёт. Платформа продолжит записывать основной объект и на фиксации получит ошибку «В данной транзакции уже происходили ошибки» — с текстом, который ничего не говорит о реальной причине.
Правильно — убрать явную транзакцию и не перехватывать исключение, либо перехватить, записать подробности и пробросить дальше:
Процедура ОбработатьПодчиненныеОбъекты()
// Транзакцию уже открыла платформа при записи объекта.
// При ошибке исключение прервёт запись, и всё откатится.
Для Каждого Ссылка Из ПолучитьЗависимыеОбъекты() Цикл
ЗависимыйОбъект = Ссылка.ПолучитьОбъект();
ЗависимыйОбъект.Согласовано = Истина;
ЗависимыйОбъект.Записать();
КонецЦикла;
КонецПроцедурыОтдельный вопрос — стоит ли вообще записывать другие объекты из ПередЗаписью. Это удлиняет транзакцию и множит блокировки; часто логичнее делать это в ПриЗаписи или отдельным действием после записи.
Пример 2. Откатываются ли переменные
а = 1;
НачатьТранзакцию();
а = 2;
ОтменитьТранзакцию();
Сообщить(а);Выведется 2. Транзакция — механизм базы данных, а переменные живут в памяти исполняемого кода; СУБД про них ничего не знает.

Что ещё не откатывается при ОтменитьТранзакцию():
| Не откатывается | Последствие |
|---|---|
| Переменные, коллекции, таблицы значений | Код дальше работает с «новыми» значениями |
| Реквизиты объектов в памяти | Объект остаётся изменённым (см. пример 3) |
| Сообщения пользователю | Пользователь видит «Успешно», хотя всё откатилось |
| Записанные файлы, отправленные письма и HTTP-запросы | Внешняя система получила данные, которых в базе нет |
Отсюда практическое правило: внешние вызовы не делают внутри транзакции. Сначала фиксируем данные, потом отправляем — или наоборот, но с отдельной обработкой расхождений. Заодно транзакция не держит блокировки, пока ждёт ответа внешнего сервиса.
Пример 3. Объект в памяти и данные по ссылке
ТоварОбъект = Справочники.Номенклатура.СоздатьЭлемент();
ТоварОбъект.Наименование = "Тест1";
ТоварОбъект.Записать();
НачатьТранзакцию();
ТоварОбъект.Наименование = "Тест2";
ТоварОбъект.Записать();
ОтменитьТранзакцию();
Сообщить(ТоварОбъект.Наименование); // Тест2
Сообщить(ТоварОбъект.Ссылка.Наименование); // Тест1Первое сообщение читает реквизит объекта в памяти: мы присвоили «Тест2», и откат базы объект не перечитал. Второе обращается через ссылку — платформа получает данные из базы, где после отката снова «Тест1».
Вывод: после отменённой транзакции объект в памяти нельзя считать актуальным. Если с ним нужно продолжать работу — перечитайте его (Прочитать() или заново ПолучитьОбъект()).
Пример 4. Повторный НачатьТранзакцию и отмена на внутреннем уровне
НачатьТранзакцию();
Посетитель1 = Справочники.Посетители.СоздатьЭлемент();
Посетитель1.Записать();
НачатьТранзакцию();
Посетитель2 = Справочники.Посетители.СоздатьЭлемент();
Посетитель2.Записать();
ОтменитьТранзакцию();
ЗафиксироватьТранзакцию();Результат: в базу не попадёт ни один элемент, а ЗафиксироватьТранзакцию() вызовет ошибку. Внутренний НачатьТранзакцию() не создал независимую транзакцию, поэтому ОтменитьТранзакцию() на втором уровне сделал неуспешной всю транзакцию целиком, включая запись первого элемента. Зафиксировать «неуспешную» транзакцию нельзя.
Если действительно нужно, чтобы ошибка в одной части не откатывала другую, это разные транзакции, выполняемые последовательно, а не вложенные.
Правильный шаблон явной транзакции
Когда явная транзакция действительно нужна (например, обработка записывает несколько объектов, которые должны сохраниться только вместе), использую шаблон из стандартов разработки:
НачатьТранзакцию();
Попытка
// Управляемая блокировка до чтения данных, которые будем менять
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
ВыполнитьСписание(Склад, Товары);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(НСтр("ru = 'Списание товаров'"),
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;Ключевые моменты:
НачатьТранзакцию()— доПопытка, чтобы при его собственной ошибке не вызватьОтменитьТранзакцию()для неначатой транзакции.ЗафиксироватьТранзакцию()— последней строкой вПопытка.- В
Исключение— сначалаОтменитьТранзакцию(), потом логирование иВызватьИсключение. Ошибку не глотаем. - Блокировку данных ставим до чтения остатков, иначе между чтением и записью их может изменить другой сеанс.
Типичные ошибки
- Явная транзакция в обработчиках записи. Она уже открыта платформой.
- Проглоченное исключение внутри транзакции. Итог — непонятная ошибка «В данной транзакции уже происходили ошибки» выше по стеку.
- Внешние вызовы и диалоги внутри транзакции. Блокировки висят, пока ждём сеть или пользователя.
- Работа с объектом после отката как с актуальным. Объект в памяти не перечитывается.
- Огромная транзакция на всю обработку. Тысячи объектов в одной транзакции блокируют пользователей и грозят откатом всей работы из-за одной ошибки. Разбивайте на порции.
Длинные транзакции и ожидания на блокировках — одна из самых частых находок при аудите производительности. При доработке 1С я проверяю транзакционную логику в первую очередь: ошибки в ней проявляются не сразу, а под нагрузкой.
Коротко
- Вложенных транзакций в 1С нет:
НачатьТранзакцию()внутри активной транзакции лишь увеличивает счётчик. ОтменитьТранзакцию()на любом уровне делает неуспешной всю транзакцию.- Откатываются только данные в базе; переменные, объекты в памяти и внешние действия — нет.
- Запись объекта уже идёт в транзакции — в
ПередЗаписьюеё открывать не нужно. - Шаблон:
НачатьТранзакцию()передПопытка, фиксация в конце, вИсключение— отмена иВызватьИсключение.




