К статье
Практический материал

Разработка и Конфигурация · 12 мин

Когда начинается транзакция в 1С: разбор механизмов и сценариев

Разбираем, когда начинается транзакция в 1С: при записи документов, проведении, обмене данными и в коде. Неявные и явные транзакции, блокировки, типичные ошибки.

Транзакция в 1С — это набор операций с базой данных, который либо выполняется целиком, либо полностью откатывается. Понимание того, в какой именно момент платформа открывает транзакцию, критично для диагностики блокировок, ошибок «Конфликт блокировок» и проблем с производительностью. Многие разработчики ошибочно считают, что транзакция существует только тогда, когда они явно вызвали НачатьТранзакцию() — на практике платформа открывает транзакции гораздо чаще.

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

Ниже разберём конкретные точки старта транзакции, отличия клиент-серверного и файлового режимов, типичные ошибки при ручном управлении и способы проверить, в какой момент реально открывается транзакция в вашем сценарии.

Неявные транзакции: когда платформа открывает их сама

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

Неявная транзакция также открывается при установке пометки удаления, непосредственном удалении объекта, записи набора регистров и при выполнении типовых механизмов обмена данными (РИБ, универсальный формат). Важно: внутри обработчиков записи вы уже находитесь в транзакции, поэтому вызов НачатьТранзакцию() там создаст вложенную логическую транзакцию, а не новую физическую.

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

Явные транзакции: метод НачатьТранзакцию()

Явная транзакция начинается строго в момент вызова метода НачатьТранзакцию() встроенного языка. С этого момента все изменения данных накапливаются, но не фиксируются в базе, пока не будет вызван ЗафиксироватьТранзакцию(). Вызов ОтменитьТранзакцию() откатывает всё, сделанное с момента старта.

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

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

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

Физическая транзакция СУБД: когда она реально открывается

В клиент-серверном варианте логическая транзакция 1С и физическая транзакция СУБД — не одно и то же. Физическая транзакция на уровне MS SQL, PostgreSQL или Oracle открывается в момент первого обращения к данным внутри логической транзакции и живёт до фиксации или отката. Именно физическая транзакция удерживает блокировки строк и таблиц.

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

Особые сценарии старта транзакции

Обмен данными и планы обмена

При загрузке сообщения обмена платформа открывает транзакцию на весь пакет или на порции объектов — в зависимости от настроек и механизма. В типовых конфигурациях загрузка через универсальный формат часто выполняется порционно, чтобы не удерживать длинную транзакцию. Если вы пишете свои правила конвертации, помните: ошибка в середине пакета при монолитной транзакции откатит уже успешно загруженные объекты.

Фоновые задания и регламентные операции

Фоновое задание само по себе транзакцию не открывает — она стартует только при первом изменении данных или явном вызове НачатьТранзакцию(). Однако типовые регламентные задания (расчёт итогов, удаление помеченных, обмены) внутри себя работают с транзакциями, и их параллельный запуск с пользовательскими операциями — типичный источник взаимоблокировок.

Проведение документов в монопольном и управляемом режиме

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

Маршрут решения

Как выбрать действие

Нужно изменить несколько связанных объектов атомарноИспользуйте явную транзакцию с блоком Попытка/Исключение
Записывается один документ или элементДостаточно неявной транзакции платформы, свою открывать не нужно
Возникают ошибки конфликта блокировок при параллельной работеСократите время транзакции: уберите из неё запросы-чтения, вычисления и интерактив
Массовая обработка тысяч объектов в одной транзакцииРазбейте на порции с фиксацией каждой порции, чтобы не держать блокировки

Как проверить, когда началась транзакция

Для диагностики используйте технологический журнал: включите события TLOCK, TDEADLOCK, TTIMEOUT и DBMSSQL/DBPOSTGRS с длительностью. По ним видно, в какой момент сеанс захватил блокировку и сколько удерживал. Событие EXCP с контекстом «Конфликт блокировки при выполнении транзакции» покажет оба конфликтующих вызова.

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

Контрольные точки

Как проверить результат

Технологический журнал, события TLOCK и TTIMEOUT
Норма

Блокировки удерживаются миллисекунды–секунды, таймаутов нет

Тревожный признак

Длительные ожидания на блокировках, рост числа TTIMEOUT

Код обработчиков записи и подписок
Норма

Внутри записи нет длительных вычислений и обращений к внешним системам

Тревожный признак

В транзакции выполняются HTTP-запросы, COM-вызовы, ожидания

Поведение при ошибке в середине пакетной обработки
Норма

Откатывается только проблемная порция, остальное зафиксировано

Тревожный признак

Откатывается вся обработка или остаются частично записанные данные

Типичные ошибки при работе с транзакциями

Первая ошибка — несбалансированные вызовы: НачатьТранзакцию() есть, а ЗафиксироватьТранзакцию() или ОтменитьТранзакцию() на одной из веток логики забыли. Результат — ошибка «Нарушена последовательность вызова операций» или «висящая» транзакция до конца вызова. Всегда оборачивайте тело в Попытка/Исключение.

Вторая ошибка — открытие транзакции «на всякий случай» вокруг одиночной записи документа. Платформа и так обеспечит атомарность, а лишняя вложенность усложняет отладку и увеличивает шанс забыть про фиксацию. Явная транзакция оправдана, только когда атомарно меняются несколько объектов или нужен контроль над точкой отката.

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

Типичные ошибки
01
Забытая фиксация или отмена на одной из веток кода — ошибка нарушения последовательности операций и незавершённая транзакция
02
Явная транзакция вокруг одиночной записи объекта — лишняя вложенность без выигрыша в атомарности
03
Интерактивные диалоги внутри транзакции — массовые конфликты блокировок у параллельных пользователей
04
Вся массовая обработка в одной транзакции — длительное удержание блокировок и откат часов работы при единичной ошибке
05
Чтение внутри транзакции без управляемых блокировок с последующей записью на основе прочитанного — потерянные обновления при параллельности

Границы безопасной самостоятельной работы

Изменение логики транзакций в типовых обработчиках и подписках — зона повышенного риска. Перед любыми экспериментами сделайте резервную копию базы и проверяйте поведение на копии под нагрузкой, имитируя параллельную работу нескольких сеансов. Ошибки, связанные с транзакциями, часто не проявляются при одном пользователе и всплывают только в реальной эксплуатации.

Если конфигурация типовая и находится на поддержке, сначала ищите решение настройками (режим управляемых блокировок, разделение итогов, расписание регламентных заданий), а не изменением кода. Снятие объектов с поддержки ради правки транзакционной логики усложнит все последующие обновления.

Итог

Что важно запомнить

Транзакция в 1С начинается автоматически при записи, проведении и удалении объектов либо явно — вызовом НачатьТранзакцию(). Физическая транзакция СУБД стартует при первом обращении к данным и удерживает блокировки до фиксации. Держите транзакции короткими, без интерактива и внешних вызовов, а массовые обработки разбивайте на порции.

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

Начинается ли транзакция при простом чтении данных запросом?

Обычный запрос вне явной транзакции выполняется в режиме автокоммита: СУБД открывает и сразу завершает короткую транзакцию чтения. Долгоживущей транзакции с удержанием блокировок при этом не создаётся — в управляемом режиме блокировок чтение вообще не блокирует данные.

Что произойдёт, если вызвать НачатьТранзакцию() внутри обработчика записи?

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

Почему возникает ошибка «Конфликт блокировок при выполнении транзакции»?

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

Можно ли отменить уже зафиксированную транзакцию?

Нет. После ЗафиксироватьТранзакцию() изменения становятся постоянными. Единственный способ вернуть состояние — восстановление из резервной копии или написание компенсирующей операции, которая программно отменит сделанные изменения.

Работают ли транзакции в файловом режиме так же, как в клиент-серверном?

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

Как узнать, сколько транзакций открыто в данный момент в коде?

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