Стоит ли использовать явные транзакции при массовом перепроведении документов в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Почему транзакция для каждого отдельного документа — это ошибка?

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

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

Использование транзакций для логических связок

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

Если "Реализация" проведется успешно, а при записи "Заказа" произойдет ошибка (например, из-за блокировки), данные в системе станут противоречивыми. В этом случае нам нужно обернуть оба действия в одну общую транзакцию. Если хоть один из объектов не запишется, мы вызовем ОтменитьТранзакцию(), и состояние системы вернется к исходному, как будто мы ничего не начинали делать. Рассмотрим, как это выглядит в коде:


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

Пакетная обработка как способ ускорения

Теперь перейдем к самому интересному — ускорению массовой обработки. При перепроведении 100 000 документов узким местом часто становится работа с дисковой подсистемой и журналом транзакций СУБД (SQL). Каждая фиксация транзакции (COMMIT) заставляет систему сбрасывать данные на диск.

Выясним причину ускорения при пакетной записи: если мы объединим, например, 500 документов в одну транзакцию, СУБД выполнит фиксацию один раз для всего пакета, а не 500 раз. Это значительно снижает накладные расходы. Разберем по шагам, как реализовать такой алгоритм:

  1. Создаем счетчик обработанных объектов.
  2. Перед началом цикла открываем транзакцию.
  3. Внутри цикла увеличиваем счетчик.
  4. При достижении определенного порога (например, 1000) фиксируем текущую транзакцию и тут же открываем новую.
  5. После завершения цикла фиксируем остаток.

Посмотрим на пример кода, реализующий этот подход:


РазмерПорции = 1000;
Счетчик = 0;

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

Если ТранзакцияАктивна() Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Критически важные риски больших транзакций

Несмотря на ускорение, у пакетной обработки есть серьезные недостатки, которые мы должны проанализировать перед применением в "боевой" базе:

1. Блокировки данных. Это самая главная проблема. Когда вы открываете транзакцию на 1000 документов, 1С блокирует все записи в регистрах, которые затрагивают эти документы. Если в этот момент другой пользователь попытается провести хотя бы один документ, попадающий в этот диапазон записей, он получит ошибку "Конфликт блокировок". В многопользовательской среде огромные транзакции могут парализовать работу всей компании.

2. Рост журнала транзакций (LDF). Пока транзакция не зафиксирована, SQL-сервер не может очистить лог. При обработке огромных массивов файл лога может разрастись до сотен гигабайт и забить все свободное место на диске сервера.

3. Риск потери прогресса. Проанализируем ситуацию: если в пакете из 1000 документов на 999-м произойдет ошибка, откатятся изменения всех 999 документов. Если вы проводите документы по одному без явной транзакции, вы потеряете только один документ.

Современная альтернатива: Фоновые задания

В современных конфигурациях на платформе 8.3 вместо манипуляций с транзакциями в одном потоке рекомендуется использовать параллельную обработку. Мы можем разбить массив из 100 000 документов на 10 частей по 10 000 и запустить их в разных фоновых заданиях. Это позволит:

Итоги и рекомендации

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

← На главную