Транзакции в 1С: НачатьТранзакцию, фиксация и откат

Дата: 21.09.2026 08:34:45
Транзакции
Блокировки
Обработка ошибок
Серверный код
Стандарты разработки
Комментарий:
0
Платформа:
8.3
Конфигурация:
Любая
Формы:
Любые

Транзакция в 1С — группа изменений базы данных, которая либо записывается целиком, либо целиком откатывается. В коде её начинают методом НачатьТранзакцию(), а завершают одним из двух способов: ЗафиксироватьТранзакцию() сохраняет изменения, ОтменитьТранзакцию() их отменяет. Синтаксис простой, но в примерах времён 8.2 его часто пишут так, что при первой же ошибке получается «В данной транзакции уже происходили ошибки!» или тихий откат, о котором никто не узнаёт. Ниже — шаблон транзакции для 1С 8.3 по стандартам 1С, разбор вложенных транзакций и ошибок, которые из них вырастают.

Когда нужна явная транзакция, а когда нет

Платформа сама открывает транзакцию при каждой записи объекта или набора записей. Обработчики ПередЗаписью, ПриЗаписи и ОбработкаПроведения выполняются уже внутри неё, и Отказ = Истина откатывает всё, что успели записать, включая движения. Поэтому оборачивать в НачатьТранзакцию() одиночный Записать() нового объекта незачем — стандарт 1С «Транзакции: правила использования» прямо называет это лишним усложнением.

Явная транзакция нужна в трёх случаях:

  • несколько записей неделимы по бизнес-логике: создаёте документ и тут же меняете связанный справочник, переносите данные из одного регистра в другой — половина результата хуже, чем ничего;
  • нужно ответственное чтение: прочитать данные, принять решение и записать, не дав никому изменить их между чтением и записью. Для этого внутри транзакции ставят управляемую блокировку БлокировкаДанных, которая держится до конца транзакции;
  • запись в регистр идёт с отключёнными итогами (УстановитьИспользованиеИтогов(Ложь)): стандарт требует выполнять отключение, запись и включение итогов в одной транзакции.

Методы транзакций доступны в серверном контексте, толстом клиенте и внешнем соединении. Из клиентского кода управляемой формы вызывайте процедуру формы с директивой &НаСервере или экспортную процедуру общего модуля с флагами «Сервер» и «Вызов сервера»; из серверного кода достаточно общего модуля с флагом «Сервер».

Как работает транзакция 1С: правильный шаблон

Пример — групповое изменение реквизитов нескольких элементов справочника. Элементы должны измениться вместе: если один не записался (не прошла проверка в ПередЗаписью, объект занят другим сеансом), остальные тоже откатываются.

// Изменяет реквизиты нескольких объектов атомарно:
// либо записываются все объекты, либо ни один.
// Контекст: сервер (общий модуль с флагом «Сервер» или &НаСервере в модуле формы).
// ЗначенияРеквизитов - Структура: ключ - имя реквизита, значение - новое значение.
Процедура ИзменитьРеквизитыОбъектов(МассивСсылок, ЗначенияРеквизитов) Экспорт

	Если МассивСсылок.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;

	НачатьТранзакцию();
	Попытка

		// Блокируем объекты до чтения, чтобы между чтением и записью
		// их никто не изменил (ответственное чтение).
		Блокировка = Новый БлокировкаДанных;
		Для Каждого Ссылка Из МассивСсылок Цикл
			ЭлементБлокировки = Блокировка.Добавить(Ссылка.Метаданные().ПолноеИмя());
			ЭлементБлокировки.УстановитьЗначение("Ссылка", Ссылка);
			ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		КонецЦикла;
		Блокировка.Заблокировать();

		Для Каждого Ссылка Из МассивСсылок Цикл
			Объект = Ссылка.ПолучитьОбъект();
			Если Объект = Неопределено Тогда
				ВызватьИсключение СтрШаблон(НСтр("ru = 'Не найден объект %1 с идентификатором %2'"),
					Ссылка.Метаданные().Представление(), Ссылка.УникальныйИдентификатор());
			КонецЕсли;
			ЗаполнитьЗначенияСвойств(Объект, ЗначенияРеквизитов);
			Объект.Записать();
		КонецЦикла;

		ЗафиксироватьТранзакцию();

	Исключение

		ОтменитьТранзакцию();

		// ОбработкаОшибок - платформа 8.3.17 и выше;
		// на более старых релизах: ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Групповое изменение реквизитов'"),
			УровеньЖурналаРегистрации.Ошибка, , ,
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));

		ВызватьИсключение;

	КонецПопытки;

КонецПроцедуры

Разбор ключевых строк

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

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

Вложенные транзакции: счётчик вместо уровней

Вложенных транзакций в 1С нет. Если НачатьТранзакцию() вызвана, когда транзакция уже идёт, новая не открывается — платформа увеличивает внутренний счётчик. ЗафиксироватьТранзакцию() уменьшает его, а реальная фиксация в СУБД происходит, только когда счётчик доходит до нуля. ОтменитьТранзакцию() на вложенном уровне тоже уменьшает счётчик и вдобавок ставит признак отмены: реального отката в этот момент нет, но зафиксировать транзакцию уже нельзя, и завершение на верхнем уровне закончится откатом всего.

Процедура из примера выше одинаково корректно работает и сама по себе, и внутри внешней транзакции:

// Обе группы должны измениться вместе: или обе, или ни одна.
Процедура ИзменитьДвеГруппы(ПерваяГруппа, ВтораяГруппа, НовыеЗначения) Экспорт

	НачатьТранзакцию();                                          // счётчик 0 -> 1, транзакция в СУБД открыта
	Попытка
		ИзменитьРеквизитыОбъектов(ПерваяГруппа, НовыеЗначения);  // внутри: 1 -> 2 -> 1
		ИзменитьРеквизитыОбъектов(ВтораяГруппа, НовыеЗначения);  // внутри: 1 -> 2 -> 1
		ЗафиксироватьТранзакцию();                               // 1 -> 0, реальная фиксация
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры
ВызовСчётчикЧто происходит в базе
НачатьТранзакцию() снаружи1транзакция открыта
НачатьТранзакцию() внутри2ничего, новой транзакции нет
ЗафиксироватьТранзакцию() внутри1ничего, изменения ещё не зафиксированы
ОтменитьТранзакцию() внутри1ставится признак отмены, зафиксировать транзакцию больше нельзя
ЗафиксироватьТранзакцию() снаружи0фиксация — или откат, если выше был признак отмены

Отсюда два следствия. Откатить «только свою часть» нельзя: отмена на любом уровне отменяет всё. А функция ТранзакцияАктивна() говорит лишь о том, что транзакция идёт, но не о том, кто её открыл: во вложенном вызове она всегда вернёт Истина.

Почему нельзя глушить исключение внутри транзакции

Если внутри транзакции произошла ошибка базы данных — не прошла запись, не удалось установить блокировку, упал запрос, — транзакция становится испорченной. Неважно, перехватили вы исключение или нет: продолжить и зафиксировать её нельзя, остаётся только отменить. Следующее обращение к базе закончится ошибкой «В данной транзакции уже происходили ошибки!». Исключения, не связанные с базой, например собственный ВызватьИсключение, транзакцию не портят, но строить на этом прикладную логику не стоит.

Код, который до сих пор встречается в обработках:

// НЕПРАВИЛЬНО: ошибка записи «проглочена» внутри транзакции
НачатьТранзакцию();
Для Каждого Ссылка Из МассивСсылок Цикл
	Попытка
		Объект = Ссылка.ПолучитьОбъект();
		Объект.Записать();
	Исключение
		// Представление ссылки - это чтение из базы,
		// а транзакция после ошибки записи уже испорчена.
		Сообщить("Не записан: " + Ссылка);
	КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();

Первая неудачная запись испортит транзакцию. Строка с Сообщить упадёт сразу за ней: чтобы получить представление ссылки, платформа читает базу. Это исключение возникает уже в блоке Исключение, его никто не ловит, и до ЗафиксироватьТранзакцию() дело не доходит — транзакция остаётся незавершённой и в итоге откатывается. Если же такой цикл вызван внутри чужой транзакции, ошибка всплывёт выше по стеку в месте, которое трудно связать с настоящей причиной.

Если задача — «обработать всё, что получится, и показать список ошибок», транзакцию открывают на каждый объект отдельно, а внешней транзакции быть не должно:

// Обрабатывает всё, что получится; ошибки возвращает списком.
// Каждый объект - в своей транзакции, внешней транзакции быть не должно.
Функция ИзменитьПоОдному(МассивСсылок, ЗначенияРеквизитов) Экспорт

	Если ТранзакцияАктивна() Тогда
		ВызватьИсключение НСтр("ru = 'Функцию нельзя вызывать внутри внешней транзакции'");
	КонецЕсли;

	Ошибки = Новый Массив;

	Для Каждого Ссылка Из МассивСсылок Цикл
		ОдинОбъект = Новый Массив;
		ОдинОбъект.Добавить(Ссылка);
		Попытка
			ИзменитьРеквизитыОбъектов(ОдинОбъект, ЗначенияРеквизитов);
		Исключение
			// Транзакция уже отменена внутри вызова, к базе обращаться можно.
			Ошибки.Добавить(СтрШаблон("%1: %2", Ссылка,
				ОбработкаОшибок.КраткоеПредставлениеОшибки(ИнформацияОбОшибке())));
		КонецПопытки;
	КонецЦикла;

	Возврат Ошибки;

КонецФункции

После ошибки внутренний вызов уже отменил свою транзакцию, снаружи транзакций нет — поэтому к базе обращаться можно, и представление ссылки в тексте ошибки получится. Проверка в начале функции защищает от вызова внутри чужой транзакции, где такая схема не работает. По той же логике строят повтор после конфликта блокировок: повторять имеет смысл только всю транзакцию целиком и только если перед её началом ТранзакцияАктивна() возвращала Ложь.

Типичные ошибки

  • «В данной транзакции уже происходили ошибки!» (в тексте стандарта 1С — «В этой транзакции уже происходили ошибки»). Обращение к базе после ошибки базы данных или после отмены на вложенном уровне. Строка, на которой упало, — следствие; причину ищите ниже по стеку в Попытка … Исключение без ВызватьИсключение. Удобно ловить остановкой по ошибке в отладчике.
  • «Транзакция не активна» при фиксации или отмене. Вызовы не парные: НачатьТранзакцию() стоит внутри Попытка после кода, который упал раньше, или транзакцию начинают в одной процедуре, а завершают в другой. Стандарт требует начинать и завершать транзакцию в одном методе.
  • «Конфликт блокировок при выполнении транзакции: Превышено максимальное время ожидания предоставления блокировки» у других пользователей. Транзакция держит блокировки до конца, а ожидание блокировки по умолчанию — 20 секунд. Не выполняйте внутри транзакции HTTP-запросы, обращения к веб-сервисам, почте, сетевым папкам и тяжёлые расчёты. Всё, что не требует блокировки, делайте до НачатьТранзакцию(), большие объёмы режьте на порции.
  • Ранний Возврат между началом и фиксацией. Транзакция остаётся открытой, держит блокировки и в итоге откатывается вместе со всем, что успели записать.
  • Если ТранзакцияАктивна() Тогда ОтменитьТранзакцию(); — страховка, которая ничего не страхует: во вложенном вызове условие истинно из-за внешней транзакции, а при нарушенной парности лишняя отмена снимет чужой уровень счётчика.
  • Сообщить(ОписаниеОшибки()) вместо ВызватьИсключение. Вызывающий код не узнаёт об ошибке и продолжает работу, будто всё записано; внешняя транзакция при этом уже не может быть зафиксирована.
  • Транзакция в клиентском коде. В тонком клиенте и веб-клиенте методов транзакций нет, модуль не пройдёт проверку. Логику переносят на сервер.

Частые вопросы

Когда использовать НачатьТранзакцию, если платформа и так пишет в транзакции?

Когда в одну неделимую операцию входят несколько записей или когда перед записью нужно заблокировать и прочитать данные. Для записи одного нового объекта явная транзакция не нужна: Записать() сама откроет и закроет её.

Нужно ли начинать транзакцию в ОбработкаПроведения или ПередЗаписью?

Нет. Эти обработчики уже выполняются внутри транзакции записи. Чтобы отменить запись и всё, что сделано в обработчиках, установите Отказ = Истина.

Можно ли отменить только часть транзакции?

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

Откатятся ли отправленное письмо, созданный файл или запись журнала регистрации?

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

Как долго может длиться транзакция?

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

Когда проще заказать

Если после доработок начались конфликты блокировок, при проведении или групповой обработке появляется «В данной транзакции уже происходили ошибки», а пользователи жалуются, что 1С тормозит, причина часто в длинных или неправильно вложенных транзакциях. Такие случаи разбирают в рамках услуги «1С тормозит». Опишите симптомы и разместите задачу — оценка бесплатная, ответ в течение рабочего дня.

По теме

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.


1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.