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

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

Начать транзакцию в 1С: когда использовать и как это работает

Разбираем, когда в 1С нужно вызывать НачатьТранзакцию, как работают вложенные транзакции, типичные ошибки и правила безопасной записи данных.

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

Многие операции в 1С уже выполняются в транзакции автоматически: проведение документа, запись объекта в обработчике, обмен данными. Поэтому ручной вызов НачатьТранзакцию() требуется далеко не всегда. Избыточное использование транзакций не только бесполезно, но и вредно: длинные транзакции удерживают блокировки, замедляют работу пользователей и увеличивают риск взаимоблокировок в клиент-серверном режиме.

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

Как устроены транзакции в платформе 1С

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

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

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

Когда транзакция создаётся автоматически и ручной вызов не нужен

Самая частая ошибка начинающих разработчиков — оборачивать в транзакцию то, что платформа и так выполняет транзакционно. Автоматически в транзакции выполняются:

  • проведение документа (обработчик ОбработкаПроведения);
  • запись объекта через Записать() и обработчики ПередЗаписью, ПриЗаписи;
  • установка пометки удаления;
  • обмен данными и многие типовые механизмы загрузки.

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

Сценарии, где НачатьТранзакцию действительно нужна

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

Создание нескольких связанных объектов

Классический пример: по данным из файла или формы нужно создать контрагента, договор и документ. Если запись документа завершится ошибкой, а контрагент уже записан, в базе останется «висячий» объект. Транзакция гарантирует, что либо появится вся связка, либо ничего.

Пакетная обработка данных

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

Загрузка данных из внешних источников

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

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

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

Один объект записывается через Записать() или документ проводитсяТранзакция не нужна — платформа выполнит операцию транзакционно сама
Несколько объектов создаются или изменяются как единое целое вне штатных обработчиковОберните все записи в НачатьТранзакцию / ЗафиксироватьТранзакцию с обработкой исключений
Обрабатывается большой массив данных в циклеОткрывайте транзакцию на каждую итерацию или небольшую порцию, а не на весь цикл
Внутри операции есть обращение к файлам, веб-сервисам, COM-объектамВынесите внешние вызовы за пределы транзакции — они не откатываются и удлиняют блокировки

Как правильно оформить код транзакции

Обязательный элемент ручной транзакции — обработка исключений с отменой. Если исключение произойдёт, а ОтменитьТранзакцию() не будет вызвана, транзакция останется «висеть» до завершения вызова сервера, что приведёт к непредсказуемым блокировкам и ошибкам в последующем коде.

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

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

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

Порядок действий
  1. 1
    Подготовьте и проверьте все данные до начала транзакции
  2. 2
    Вызовите НачатьТранзакцию() непосредственно перед первой записью
  3. 3
    Выполните все записи внутри блока Попытка
  4. 4
    Вызовите ЗафиксироватьТранзакцию() после последней записи
  5. 5
    В блоке Исключение вызовите ОтменитьТранзакцию() и пробросьте ошибку дальше
  6. 6
    Проверьте на тестовой базе, что при искусственной ошибке данные не остаются частично записанными

Ограничения и риски при работе с транзакциями

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

Второй риск — взаимоблокировки. Если два сеанса в транзакциях изменяют одни и те же объекты в разном порядке, СУБД может обнаружить deadlock и аварийно завершить одну из транзакций. Снизить вероятность помогает единый порядок обращения к объектам во всех процедурах и минимизация длительности транзакций.

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

Типичные ошибки и как их избежать

Типичные ошибки
01
Транзакция без обработки исключений — при ошибке транзакция не отменяется явно, блокировки висят до конца вызова, последующий код падает с непонятными ошибками
02
Одна транзакция на весь цикл обработки тысяч объектов — массовые блокировки, таймауты у пользователей, при сбое в конце откатывается многая работа
03
ОтменитьТранзакцию() без проверки ТранзакцияАктивна() в сложной логике — ошибка 'Транзакция не активна' маскирует исходное исключение
04
Фиксация транзакции внутри обработчика проведения — нарушается атомарность штатного механизма, часть данных остаётся при откате
05
Вызов веб-сервиса или отправка письма внутри транзакции — при откате внешнее действие уже выполнено, а в базе его нет

Отдельно стоит упомянуть ошибку «проглатывания» исключений: конструкция, где в блоке Исключение транзакция отменяется, но ошибка никуда не записывается и не пробрасывается. Внешне обработка завершается «успешно», а данные на самом деле не записаны. Как минимум фиксируйте ошибку в журнале регистрации через ЗаписьЖурналаРегистрации.

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

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

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

Все объекты операции отсутствуют в базе, ошибка видна пользователю или в журнале регистрации

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

Часть объектов записана, часть нет — транзакция не работает или фиксируется раньше времени

Длительность транзакции
Норма

Между НачатьТранзакцию и ЗафиксироватьТранзакцию только записи в базу, без чтения файлов и внешних вызовов

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

Внутри транзакции циклы ожидания, диалоги, обращения к сети

Работа других пользователей во время обработки
Норма

Проведение документов и запись справочников проходят без таймаутов

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

Массовые ошибки превышения ожидания блокировки — транзакции слишком длинные или широкие

Частые вопросы о транзакциях в 1С

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

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

Что произойдёт, если забыть вызвать ЗафиксироватьТранзакцию() или ОтменитьТранзакцию()?

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

Нужна ли транзакция при записи одного элемента справочника?

Нет. Метод Записать() сам по себе выполняется в транзакции. Ручная обёртка ничего не добавит, кроме лишнего уровня вложенности. Транзакция нужна, только когда вместе с этим элементом должны атомарно измениться другие объекты.

Как обработать большой объём данных, не блокируя работу пользователей?

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

Почему возникает ошибка «В данной транзакции уже происходили ошибки»?

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

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

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

Итог

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

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