Как организовать порционное чтение и обработку таблицы значений в 1С

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

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

Метод 1: Программное разделение таблицы на массив частей

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

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


&НаСервере
Функция РазбитьТЗНаЧасти(ВходящаяТЗ, РазмерЧасти)
    
    Результат = Новый Массив; // Здесь будут храниться части
    МассивСтрок = ВходящаяТЗ.ВыгрузитьКолонку("Данные"); // Или просто итерация по строкам
    
    ТекущаяПорция = Новый Массив;
    Счетчик = 0;
    
    Для Каждого СтрокаТЗ Из ВходящаяТЗ Цикл
        ТекущаяПорция.Добавить(СтрокаТЗ);
        Счетчик = Счетчик + 1;
        
        Если Счетчик = РазмерЧасти Тогда
            Результат.Добавить(ТекущаяПорция);
            ТекущаяПорция = Новый Массив;
            Счетчик = 0;
        КонецЕсли;
    КонецЦикла;
    
    // Добавляем остаток, если он есть
    Если ТекущаяПорция.Количество() > 0 Тогда
        Результат.Добавить(ТекущаяПорция);
    КонецЕсли;
    
    Возврат Результат;
     
КонецФункции

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

Метод 2: Порционное чтение из базы данных через сравнение ссылок

Выясним причину, по которой классическая пагинация (как в SQL через OFFSET) недоступна напрямую в языке запросов 1С. Платформа не поддерживает пропуск определенного количества строк, поэтому для порционного чтения из БД мы используем метод сравнения ссылок. Чтобы глубже понять, как такие запросы влияют на СУБД, можно проанализировать SQL сервер глазами 1С-ника.

Суть метода заключается в выборе первых N записей, которые «больше» последней обработанной ссылки. Проанализируем ситуацию на примере:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ ПЕРВЫЕ 1000
|	Номенклатура.Ссылка КАК Ссылка
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	Номенклатура.Ссылка > &ПоследняяСсылка
|УПОРЯДОЧИТЬ ПО
|	Ссылка";

ПоследняяСсылка = Справочники.Номенклатура.ПустаяСсылка();

Пока Истина Цикл
    Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылка);
    РезультатЗапроса = Запрос.Выполнить();
    
    Если РезультатЗапроса.Пустой() Тогда
        Прервать;
    КонецЕсли;
    
    Выгрузка = РезультатЗапроса.Выгрузить();
    // Обрабатываем порцию данных...
    
    ПоследняяСсылка = Выгрузка[Выгрузка.Количество() - 1].Ссылка;
КонецЦикла;

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

Метод 3: Использование временных таблиц и функции АВТОНОМЕРЗАПИСИ

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

  1. Помещаем исходную ТаблицаЗначений во временную таблицу.
  2. Используем функцию АВТОНОМЕРЗАПИСИ() для создания индекса каждой строки.
  3. Выбираем данные, используя условие ГДЕ НомерСтроки МЕЖДУ &Начало И &Конец.

Посмотрим на пример запроса:


// ВТ_Данные уже содержит ваши строки
Запрос.Текст = 
"ВЫБРАТЬ
|	АВТОНОМЕРЗАПИСИ() КАК НомерСтроки,
|	Т.Ссылка КАК Товар
|ПОМЕСТИТЬ ВТ_Индексированная
|ИЗ
|	ВТ_Данные КАК Т
|;
|ВЫБРАТЬ
|	Т.Товар
|ИЗ
|	ВТ_Индексированная КАК Т
|ГДЕ
|	Т.НомерСтроки МЕЖДУ &Начало И &Конец";

Обратите внимание: функция АВТОНОМЕРЗАПИСИ() может начинать нумерацию не с единицы в некоторых версиях платформы или при определенных настройках СУБД. Рекомендуется сначала получить минимальный и максимальный номер из ВТ_Индексированная, чтобы корректно рассчитать границы порций.

Метод 4: Оптимизация передачи в фоновые задания

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

Рассмотрим правильный алгоритм:

Пример использования метода Скопировать() для выделения порции:


// Параметры: МассивСтрокДляОбработки - массив объектов СтрокаТаблицыЗначений
ПорцияТЗ = ИсходнаяТЗ.Скопировать(МассивСтрокДляОбработки);

Метод 5: Использование Выборки вместо Выгрузки

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

Для порционной обработки (поможет инструмент для эффективного чтения больших таблиц) на уровне кода 1С это выглядит так:


Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    // Обработка по одной строке
    // Чтобы имитировать порции, можно использовать счетчик:
    Если Счетчик % РазмерПорции = 0 Тогда
        // Выполнить промежуточную фиксацию данных или очистку памяти
    КонецЕсли;
КонецЦикла;

Рекомендации по выбору метода

Выбор конкретного способа зависит от контекста задачи. Перед внедрением сложных алгоритмов мы рекомендуем провести замер производительности контура 1С и поиск узких мест, чтобы точно определить причину задержек. Основные правила выбора:

  1. Если данные в памяти и их нужно распределить по потокам — используйте передачу индексов во Временное Хранилище.
  2. Если данных в базе очень много (миллионы) — используйте запрос с условием по ссылке (ГДЕ Ссылка > &ПоследняяСсылка).
  3. Если нужно быстро «нарезать» существующую таблицу на куски для последовательной обработки — используйте метод Скопировать() с массивом строк.
  4. Для экономии памяти при обработке в один поток — рассмотрите возможность удаления обработанных строк из исходной таблицы методом ТЗ.Удалить(0), чтобы таблица уменьшалась в процессе цикла.

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

← На главную