При разработке механизмов импорта данных из внешних источников (JSON, Excel, XML) перед разработчиком часто встает дилемма: как быстро и с минимальной нагрузкой на сервер сопоставить данные из файла с существующими объектами в базе (номенклатурой, сериями, контрагентами). В этой статье мы рассмотрим три основных подхода, разберем технические нюансы работы платформы 1С и выясним, почему стандартные решения не всегда являются оптимальными.
Начнем с анализа ситуации, когда идентификация происходит по уникальному идентификатору (GUID). Многие разработчики опасаются, что создание ссылки в цикле порождает множество запросов к СУБД. Проанализируем ситуацию подробнее.
Метод Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(ЗначениеИзФайла)) выполняется исключительно в оперативной памяти сервера приложений. Он не обращается к базе данных в момент вызова. Платформа просто конструирует объект типа «Ссылка», зная его GUID.
Однако здесь кроется важный нюанс, который мы должны учитывать: это «ленивое» действие. Если в вашей базе данных объекта с таким идентификатором не существует, 1С не выдаст ошибку сразу. Проблема проявится позже, когда вы попытаетесь прочитать свойства этой ссылки (например, Наименование) или записать документ. Такая ссылка окажется «битой» (Объект не найден).
Рассмотрим пример простейшей обработки в цикле:
Пока ЧтениеJSON.Прочитать() Цикл
Если ИмяТекущегоСвойства = "partNumber" Тогда
// Создаем ссылку в памяти без обращения к СУБД
л_Серия = Справочники.СерииНоменклатуры.ПолучитьСсылку(Новый УникальныйИдентификатор(ЧтениеJSON.ТекущееЗначение));
// ВНИМАНИЕ: Проверка на существование объекта не происходит!
КонецЕсли;
КонецЦикла;
Если же вам нужно искать объекты по артикулу или коду, используя НайтиПоКоду() или НайтиПоНаименованию(), то каждый такой вызов в цикле — это полноценный запрос к СУБД. Посмотрим на рекомендации: такой подход допустим только при очень малом количестве итераций (до 20–50 строк). Если строк в файле больше, накладные расходы на установку соединения и парсинг запроса будут расти лавинообразно.
Второй популярный подход — это загрузить весь справочник (например, 100 000 товаров) в память в виде таблицы значений перед началом обработки файла. Разберем по шагам, с какими трудностями мы можем столкнуться.
Главная проблема этого метода — потребление оперативной памяти. Справочник серий может содержать миллионы записей. Загрузка такого объема данных в ТаблицаЗначений может вызвать ошибку Out of memory, особенно на 32-битных серверах 1С. Даже на 64-битных системах это создаст колоссальную нагрузку на рабочий процесс rphost.
Если вы все же решили использовать этот метод, важно помнить об индексации. Поиск методом Найти() по неиндексированной таблице на большом объеме будет крайне медленным, так как 1С будет выполнять полный перебор строк.
Посмотрим на пример правильной подготовки ТЗ для поиска:
// Загружаем данные один раз
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка, Код ИЗ Справочник.Номенклатура");
ТЗ_Номенклатура = Запрос.Выполнить().Выгрузить();
// ОБЯЗАТЕЛЬНО добавляем индекс для мгновенного поиска
ТЗ_Номенклатура.Индексы.Добавить("Код");
// Теперь поиск в цикле будет работать быстро
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
НайденнаяСтрока = ТЗ_Номенклатура.Найти(СтрокаФайла.Код, "Код");
Если НайденнаяСтрока <> Неопределено Тогда
СсылкаНаТовар = НайденнаяСтрока.Ссылка;
КонецЕсли;
КонецЦикла;
Наиболее производительным и профессиональным способом считается использование временных таблиц (ВТ). Суть метода заключается в том, чтобы передать данные из внешнего файла в один запрос и доверить сопоставление самой СУБД. Проанализируем этот процесс.
ЛЕВОЕ СОЕДИНЕНИЕ временной таблицы с таблицами справочников базы данных.При таком подходе мы получаем результат одним пакетом. Выясним причину высокой скорости: СУБД оптимизирует поиск, используя индексы физических таблиц, и возвращает только нужные нам данные.
Рассмотрим пример реализации такого запроса:
// Предположим, ТЗ_ИзФайла уже заполнена данными из файла
// Очень важно, чтобы колонки в ТЗ имели четкое ОписаниеТипов
Запрос = Новый Запрос;
Запрос.МенеджерВременных Таблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеФайла ИЗ &ТЗ_ИзФайла КАК ТЗ_ИзФайла;
|
|ВЫБРАТЬ
| ВТ.АртикулФайла,
| Номенклатура.Ссылка КАК ТоварСсылка
|ИЗ
| ВТ_ДанныеФайла КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВТ.АртикулФайла = Номенклатура.Артикул";
Запрос.УстановитьПараметр("ТЗ_ИзФайла", ТЗ_ИзФайла);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Если файл содержит сотни тысяч строк, даже передача одной огромной ТЗ в запрос может замедлить работу. В этом случае рассмотрим подробнее стратегию разделения данных на порции (чанки).
Мы можем разбивать исходную таблицу на блоки по 1000–5000 строк. Для каждой порции можно запускать отдельное фоновое задание или обрабатывать их последовательно, используя один МенеджерВременныхТаблиц. Это позволит держать потребление оперативной памяти в разумных пределах и избежать блокировок таблиц базы данных.
Проанализируем ситуацию с созданием недостающих элементов. Если при сопоставлении выяснилось, что части товаров нет в базе, рекомендуется сначала собрать все отсутствующие позиции в отдельный массив и создать их «пачкой» в одной транзакции. Только после этого следует приступать к созданию итогового документа заказа. Это значительно снижает риск дедлоков (взаимных блокировок) в высоконагруженных системах.
Подведем итог нашему исследованию:
ПолучитьСсылку(), это самый дешевый по ресурсам метод.Надеемся, что этот детальный разбор поможет вам выбрать оптимальную стратегию для ваших задач по интеграции и сделает ваши решения в 1С более быстрыми и надежными!