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