При работе с большими объемами данных в 1С, например, при выгрузке характеристик номенклатуры (ширина, высота, вес, остатки, изображения, штрихкоды) в текстовый файл, часто возникает проблема низкой производительности и ошибок "Недостаточно памяти". Изначальный подход, когда на каждую позицию номенклатуры в цикле выполняется отдельный запрос для получения ее характеристик, является крайне неэффективным. Давайте разберем, как мы можем оптимизировать этот процесс, сделав его значительно быстрее и надежнее.
Мы рассмотрим несколько подходов к решению этой задачи, начиная с базовых оптимизаций и заканчивая более сложными методами для очень больших объемов данных.
Первое и наиболее очевидное решение – это минимизация обращений к базе данных. Вместо того чтобы выполнять множество мелких запросов в цикле для каждой позиции номенклатуры, мы постараемся получить все необходимые данные одним большим запросом. Это значительно сократит время выполнения, поскольку уменьшится накладные расходы на установление соединения с базой данных и передачу данных по сети.
Номенклатура, регистров накопления (например, СвободныеОстатки для получения остатков), а также таблиц, содержащих габариты, штрихкоды и ссылки на картинки.Выборку результатов. Крайне важно обрабатывать эти данные, перебирая Выборку напрямую, не выгружая ее предварительно в ТаблицуЗначений. Выгрузка всей Выборки в ТаблицуЗначений может привести к загрузке всего объема данных в оперативную память, что может вызвать ошибки "Недостаточно памяти" при очень больших объемах. Работа напрямую с Выборкой позволяет системе управлять загрузкой данных более эффективно, иногда порционно.Рассмотрим пример структуры такого запроса. Предположим, нам нужны Артикул, Наименование, текущий остаток, ширина, высота, вес и штрихкод для каждой номенклатуры, которая есть в наличии.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Вес КАК Вес,
| Номенклатура.Ширина КАК Ширина,
| Номенклатура.Высота КАК Высота,
| СвободныеОстатки.ВНаличииОстаток КАК Остаток,
| // Добавляем поля для картинок, штрихкодов и т.п.
| ЕСТЬNULL(НоменклатураПрисоединенныеФайлы.Файл, НЕОПРЕДЕЛЕНО) КАК Картинка,
| Штрихкоды.Штрихкод КАК Штрихкод
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки КАК СвободныеОстатки
| ПО Номенклатура.Ссылка = СвободныеОстатки.Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НоменклатураПрисоединенныеФайлы КАК НоменклатураПрисоединенныеФайлы
| ПО Номенклатура.Ссылка = НоменклатураПрисоединенныеФайлы.Объект
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
| ПО Номенклатура.Ссылка = Штрихкоды.Владелец
|ГДЕ
| СвободныеОстатки.ВНаличииОстаток > 0"; // Отбираем только те, что есть на остатках
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь мы обрабатываем данные и записываем их в файл
// Например:
// СтрокаДляФайла = Выборка.Артикул + ";" + Выборка.Наименование + ";" + Выборка.Остаток;
// ...
КонецЦикла;
Важно помнить, что для виртуальных таблиц, таких как Остатки, при получении текущих остатков дату в запрос передавать не нужно. Это позволяет использовать более оптимизированные таблицы текущих остатков, а не таблицы движений, что может значительно ускорить запрос. Также рекомендуется использовать все измерения во внешнем запросе для виртуальной таблицы.
После того как мы оптимизировали получение данных, нам нужно убедиться, что их запись в файл также эффективна. Использование объекта ТекстовыйДокумент для записи больших объемов данных является нежелательным, так как этот объект загружает весь файл целиком в оперативную память. При больших файлах это неизбежно приведет к ошибкам нехватки памяти.
Вместо ТекстовыйДокумент мы должны использовать объект ЗаписьТекста.
ЗаписьТекста: Этот объект работает в модели последовательного доступа, то есть он записывает данные в файл по мере их поступления, не храня весь файл в оперативной памяти. Это позволяет работать с файлами практически любого размера.Рассмотрим пример использования ЗаписьТекста в комбинации с выборкой данных:
ПутьКФайлу = "C:\Temp\ХарактеристикиНоменклатуры.txt";
Кодировка = КодировкиТекста.UTF8; // Или любая другая необходимая кодировка
Разделитель = Символы.Таб; // Или ";" или ","
Попытка
Запись = Новый ЗаписьТекста(ПутьКФайлу, Кодировка);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Артикул,
| Номенклатура.Наименование,
| СвободныеОстатки.ВНаличииОстаток
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки КАК СвободныеОстатки
| ПО Номенклатура.Ссылка = СвободныеОстатки.Номенклатура
|ГДЕ
| СвободныеОстатки.ВНаличииОстаток > 0";
Выборка = Запрос.Выполнить().Выбрать();
// Записываем заголовок файла
Запись.ЗаписатьСтроку("Артикул" + Разделитель + "Наименование" + Разделитель + "Остаток");
Пока Выборка.Следующий() Цикл
СтрокаДляЗаписи = Выборка.Артикул + Разделитель
+ Выборка.Наименование + Разделитель
+ Строка(Выборка.ВНаличииОстаток);
Запись.ЗаписатьСтроку(СтрокаДляЗаписи);
КонецЦикла;
Исключение
Сообщить("Ошибка при записи файла: " + ОписаниеОшибки());
КонецПопытки;
Если Запись <> Неопределено Тогда
Запись.Закрыть();
КонецЕсли;
Несмотря на оптимизацию запросов, иногда объем данных настолько велик (сотни тысяч или миллионы позиций), что даже единый запрос может привести к чрезмерной загрузке оперативной памяти при формировании Выборки или выполнении очень сложных соединений. В таких случаях нам могут помочь механизмы порционной обработки данных и параллелизации.
Для обхода элементов справочников без загрузки всех данных в память мы можем использовать объект СправочникВыборка. Этот объект возвращается методами Выбрать и ВыбратьИерархически у объекта типа СправочникМенеджер (например, Справочники.Номенклатура.Выбрать()). Ключевая особенность СправочникВыборка заключается в том, что он обходит элементы динамически, выбирая их порциями из базы данных. Такой подход позволяет эффективно работать с большими списками справочников, не загружая их целиком в память.
ВыборкаНоменклатуры = Справочники.Номенклатура.Выбрать();
Пока ВыборкаНоменклатуры.Следующий() Цикл
// ВыборкаНоменклатуры.Ссылка - ссылка на текущий элемент
// ВыборкаНоменклатуры.Наименование - наименование текущего элемента
// И так далее для других реквизитов, которые можно получить без отдельного запроса
// Например, если вес, ширина, высота - это реквизиты самого справочника Номенклатура,
// то их можно получить напрямую из выборки.
// Для получения остатков и других данных, требующих сложных соединений или регистров,
// мы можем использовать кэшированный запрос с параметром.
КонецЦикла;
Однако СправочникВыборка не позволяет напрямую получить данные из регистров (например, остатки) или других связанных таблиц без выполнения дополнительных запросов. Поэтому мы часто используем комбинированный подход.
Если нам нужно получить много данных по каждой позиции (например, остатки, которые хранятся в регистре), но при этом мы хотим минимизировать загрузку самой номенклатуры в память, мы можем объединить СправочникВыборка с параметризованным запросом.
СправочникВыборка: Мы используем СправочникВыборка для порционного получения ссылок на номенклатуру.СправочникВыборка мы выполняем один и тот же запрос для получения остатков, габаритов, штрихкодов и других характеристик, передавая в него ссылку на текущую номенклатуру как параметр. Платформа 1С оптимизирует повторные выполнения одного и того же запроса с разными параметрами, используя кэширование плана запроса, что значительно ускоряет его работу по сравнению с формированием нового запроса каждый раз.
ЗапросОстатков = Новый Запрос;
ЗапросОстатков.Текст =
"ВЫБРАТЬ
| СвободныеОстатки.ВНаличииОстаток КАК Остаток,
| Штрихкоды.Штрихкод КАК Штрихкод,
| НоменклатураПрисоединенныеФайлы.Файл КАК Картинка
|ИЗ
| РегистрНакопления.СвободныеОстатки КАК СвободныеОстатки
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
| ПО СвободныеОстатки.Номенклатура = Штрихкоды.Владелец
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НоменклатураПрисоединенныеФайлы КАК НоменклатураПрисоединенныеФайлы
| ПО СвободныеОстатки.Номенклатура = НоменклатураПрисоединенныеФайлы.Объект
|ГДЕ
| СвободныеОстатки.Номенклатура = &Номенклатура
| И СвободныеОстатки.ВНаличииОстаток > 0"; // Отбираем только те, что есть на остатках
ВыборкаНоменклатуры = Справочники.Номенклатура.Выбрать();
Пока ВыборкаНоменклатуры.Следующий() Цикл
// Устанавливаем параметр для запроса
ЗапросОстатков.УстановитьПараметр("Номенклатура", ВыборкаНоменклатуры.Ссылка);
РезультатОстатков = ЗапросОстатков.Выполнить().Выбрать();
Если РезультатОстатков.Следующий() Тогда
// Здесь мы имеем доступ к данным из СправочникВыборка и результатам запроса
СтрокаДляЗаписи = ВыборкаНоменклатуры.Артикул + Разделитель
+ ВыборкаНоменклатуры.Наименование + Разделитель
+ Строка(РезультатОстатков.Остаток) + Разделитель
+ РезультатОстатков.Штрихкод;
Запись.ЗаписатьСтроку(СтрокаДляЗаписи);
КонецЕсли;
КонецЦикла;
Для максимальной производительности и обработки экстремально больших объемов данных, когда даже порционная выборка последовательно занимает слишком много времени, мы можем прибегнуть к параллельной обработке с использованием фоновых заданий. Это особенно актуально для выгрузки на сайт, где время ответа критично.
СправочникВыборка для своей порции данных и записывает результат в отдельный временный текстовый файл.Этот подход значительно сокращает общее время обработки, используя возможности многоядерных процессоров сервера 1С. Однако он требует более сложной реализации и координации между заданиями.
Помимо рассмотренных подходов, мы всегда должны помнить об общих принципах оптимизации запросов в 1С:
Остатки, важно правильно формировать условия. Как мы уже упоминали, для получения текущих остатков дату в запрос передавать не нужно, чтобы не задействовать таблицы движений. Всегда стараемся использовать все доступные измерения во внешнем запросе для виртуальной таблицы.ВЫРАЗИТЬ(Поле КАК Тип).ИЛИ в условиях отбора запроса может снизить производительность, так как может привести к менее эффективному использованию индексов. В таких случаях мы можем разбить запрос на два отдельных и объединить результаты через ОБЪЕДИНИТЬ ВСЕ или использовать конструкцию Поле В (Значение1, Значение2).ВЫБРАТЬ *. Это уменьшает объем передаваемых данных и нагрузку на сервер 1С и СУБД.Номенклатура.Поставщик.Страна) в запросе является эффективным способом получения данных из связанных справочников и регистров.Помимо оптимизации самого кода, мы не должны забывать о настройках серверной части 1С, которые могут существенно влиять на производительность и потребление памяти:
rphost и позволяет использовать значительно больше оперативной памяти.Выборка.Ссылка.ПолучитьОбъект(), если нам нужны только некоторые реквизиты объекта, мы должны стремиться получить их сразу в запросе.Таким образом, для эффективной выгрузки характеристик номенклатуры в текстовый файл мы должны применять комплексный подход: сначала максимально объединить данные в один запрос, затем использовать ЗаписьТекста для эффективной записи в файл, а для очень больших объемов рассмотреть порционную обработку через СправочникВыборка или фоновые задания. Не забываем также об общих принципах оптимизации запросов и настройках серверной платформы 1С для достижения наилучших результатов.