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

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

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

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

Анализ исходной проблемы: почему код работает медленно

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

  1. Обращение к СУБД: Каждый вызов ПолучитьОбъект() — это отдельный тяжелый запрос к базе данных для считывания всех полей документа.
  2. Затраты на запись: Метод Записать() инициирует начало транзакции, проверку прав доступа, выполнение всех подписок на события (перед записью, при записи) и фиксацию изменений в БД.
  3. Блокировки: На время записи объект блокируется. Если в запросе одна и та же ссылка на документ встречается 10 раз, система 10 раз откроет, изменит и закроет этот документ.

Чтобы исправить это, нам нужно реализовать принцип: один документ — одна запись.

Шаг 1: Оптимизация запроса с помощью итогов

Первым делом внесем изменения в текст запроса. Чтобы эффективно обойти результат, нам нужно сгруппировать данные по документам-приемникам прямо в запросе. Для этого в конец запроса добавим предложение ИТОГИ. Проанализируем структуру: основным полем группировки будет ДокументПриемник.


// ... (весь предыдущий текст запроса автора)
ИТОГИ
    СУММА(СуммаОплаты)
ПО
    ДокументПриемник

Использование итогов позволит нам получить иерархическую структуру выборки: на верхнем уровне будут документы, а на вложенном — строки их будущих табличных частей.

Шаг 2: Реализация эффективного обхода (Два цикла)

Вместо плоской выборки мы будем использовать метод Выбрать с параметром ОбходРезультатаЗапроса.ПоГруппировкам. Это позволит нам один раз получить объект документа, заполнить все его строки и один раз записать его.

Рассмотрим пошагово обновленный программный код:


РезультатЗапроса = Запрос.Выполнить();

// Начинаем выборку по группировкам (по документам)
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаДокумент.Следующий() Цикл
    
    // Проверяем на пустую ссылку или некорректные данные
    Если Не ЗначениеЗаполнено(ВыборкаДокумент.ДокументПриемник) Тогда
        Продолжить;
    КонецЕсли;

    // Получаем объект документа ОДИН раз
    ДокОбъект = ВыборкаДокумент.ДокументПриемник.ПолучитьОбъект();
    
    // Очищаем ТЧ, если это необходимо по логике задачи
    // ДокОбъект.СМ_ВидыОплаты.Очистить();

    // Открываем вложенную выборку по детальным записям (строкам)
    ВыборкаСтрок = ВыборкаДокумент.Выбрать();
    
    Пока ВыборкаСтрок.Следующий() Цикл
        // Добавляем строки в ТЧ документа
        НоваяСтрока = ДокОбъект.СМ_ВидыОплаты.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаСтрок);
        // Или заполняем вручную:
        // НоваяСтрока.ВидОплаты = ВыборкаСтрок.ВидОплаты;
        // НоваяСтрока.Сумма = ВыборкаСтрок.СуммаОплаты;
    КонецЦикла;

    // Записываем документ ОДИН раз после заполнения всех строк
    Попытка
        ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
    Исключение
        Инфо = ИнформацияОбОшибке();
        ЗаписьЖурналаРегистрации("ОбработкаЗаполнения", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка записи документа " + ДокОбъект.Ссылка + ": " + ПодробноеПредставлениеОшибки(Инфо));
    КонецПопытки;

КонецЦикла;

Шаг 3: Устранение конфликтов блокировок

Если во время работы вашей обработки кто-то другой (пользователь или регламентное задание) попытается изменить тот же документ, возникнет ошибка "Конфликт блокировок". Чтобы программа работала стабильно, необходимо использовать механизм управляемых блокировок.

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


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ОтчетОРозничныхПродажах"); // Укажите нужный тип
ЭлементБлокировки.УстановитьЗначение("Ссылка", ВыборкаДокумент.ДокументПриемник);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

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

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

Шаг 4: Использование метода Загрузить() для максимального ускорения

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

Проанализируем алгоритм:

  1. Выгружаем результат запроса целиком в ТаблицаЗначений.
  2. В основном цикле по документам создаем копию этой таблицы, отфильтрованную по текущему документу.
  3. Используем метод ДокументОбъект.ИмяТЧ.Загрузить(ОтфильтрованнаяТаблица).

Это исключает накладные расходы на создание каждой строки через Добавить(). Однако помните, что имена колонок в таблице значений должны в точности совпадать с именами реквизитов табличной части документа.

Дополнительные советы по оптимизации

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

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

← На главную