Почему программная отмена проведения документов в цикле вызывает блокировки, а интерактивная — нет?

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

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

Анализируем причины возникновения блокировок

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

  1. Эскалация блокировок на уровне СУБД: При выполнении множества быстрых транзакций записи подряд (что и происходит в цикле), SQL-сервер может решить, что управление тысячами мелких блокировок строк (Row Locks) слишком ресурсоемко. В этот момент происходит «эскалация» — замена множества мелких блокировок на одну монопольную блокировку всей таблицы. Если в этот момент другой пользователь пытается просто прочитать данные из этой таблицы, возникает конфликт.
  2. Отсутствие микропауз: Программный цикл выполняется на предельной скорости процессора. Интерактивное же удаление в форме списка, даже если выделено много строк, инициируется платформой с определенными задержками на обновление интерфейса, обработку событий формы и подтверждений. Эти миллисекунды позволяют СУБД «разгребать» очередь транзакций.
  3. Порядок обхода: В программном коде мы часто идем по выборке, отсортированной по ссылке или вообще без сортировки. Если другие пользователи в этот момент проводят документы, их транзакции могут захватывать таблицы регистров в ином порядке. Это классический путь к Deadlock (взаимоблокировке).

Способ 1. Использование явных блокировок в запросе

Рассмотрим вариант, предложенный опытными разработчиками: предварительную блокировку данных. Если мы заранее сообщим системе, что собираемся изменять эти объекты, менеджер блокировок 1С выстроит очередь доступа более корректно. Для этого в тексте запроса, который формирует выборку, следует использовать конструкцию ДЛЯ ИЗМЕНЕНИЯ.

Проанализируем пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   РеализацияТоваровУслуг.Ссылка КАК Ссылка
    |ИЗ
    |   Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
    |ГДЕ
    |   РеализацияТоваровУслуг.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    |ДЛЯ ИЗМЕНЕНИЯ"; // Блокируем выбранные объекты для текущей транзакции

Выборка = Запрос.Выполнить().Выбрать();

Важный нюанс: Использование ДЛЯ ИЗМЕНЕНИЯ эффективно только внутри явной транзакции. Однако стоит помнить, что удержание большой транзакции может парализовать работу других пользователей. Поэтому этот метод лучше комбинировать с пакетной обработкой.

Способ 2. Использование режима загрузки данных

Посмотрим на ситуацию со стороны оптимизации бизнес-логики. Метод УстановитьПометкуУдаления неявно вызывает отмену проведения и перезапись объекта. При этом запускаются все подписки на события, проверки в модулях объекта (ПередЗаписью, ПриЗаписи) и пересчеты. Если мы уверены в корректности данных, мы можем существенно ускорить процесс, установив флаг ОбменДанными.Загрузка.

Рассмотрим реализацию:


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

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

Способ 3. Пакетная обработка с паузами

Выясним, как избежать эскалации блокировок. Если документов очень много, не стоит обрабатывать их все в одном цикле «на вылет». Правильнее будет разбивать обработку на порции (пакеты) и делать небольшие технологические паузы.

Разберем алгоритм по шагам:

  1. Определяем размер порции (например, 50 или 100 документов).
  2. После обработки каждой порции фиксируем транзакцию (если мы ее открывали явно) или просто делаем небольшую паузу.
  3. Это дает возможность СУБД завершить текущие операции записи и обновить индексы.

Пример реализации с «микропаузой»:


Счетчик = 0;
Пока Выборка.Следующий() Цикл
    ДокОбъект = Выборка.Ссылка.ПолучитьОбъект();
    ДокОбъект.УстановитьПометкуУдаления(Истина);
    
    Счетчик = Счетчик + 1;
    // Через каждые 50 документов даем системе "выдохнуть"
    Если Счетчик % 50 = 0 Тогда
        #Если Клиент Тогда
            // В новых версиях платформы можно использовать Ожидать() или аналоги
            // Для сервера можно использовать вызов фонового задания или простой цикл ожидания
        #КонецЕсли
    КонецЕсли;
КонецЦикла;

Способ 4. Прямая очистка движений (для продвинутых задач)

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

Проанализируем этот подход:

Вместо стандартного механизма, мы можем для каждого регистра, по которому делает движения документ, выполнить следующий код:


Набор = РегистрыНакопления.Продажи.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Выборка.Ссылка);
Набор.Записать(); // Записываем пустой набор (очищаем движения)

После очистки всех движений, вызов УстановитьПометкуУдаления(Истина) пройдет мгновенно, так как системе не нужно будет анализировать таблицы итогов и удалять тысячи записей — они уже удалены точечно.

Подведение итогов

Мы проанализировали ситуацию и выяснили, что основной причиной конфликтов является высокая плотность транзакций в программном цикле. Интерактивная работа в форме списка более «щадящая» для СУБД из-за внутренних механизмов платформы, которые могут группировать запросы или выполнять их в асинхронном режиме.

Для решения проблемы в коде рекомендуется:

← На главную