Как оптимизировать загрузку XML через XDTO в табличную часть документа 1С

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

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

Анализ исходного кода и причин замедления

Проанализируем базовый сценарий: из входящего XML-файла с помощью фабрики XDTO считывается структура данных, после чего выполняется обход свойства и заполнение строк табличной части документа. Ниже приведен пример исходного кода, вызывающего задержки:


// Чтение XML в ОбъектXDTO
ЧтениеXML.УстановитьСтроку(XMLСтрока);
Об = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, Тип_);

// Заполнение табличной части SLUCH
НС = Объект.SLUCH.Добавить();
Для Каждого Эл Из Об.SLUCH[0].Свойства() Цикл
    ЗаполнитьПоле(НС[Эл.Имя], Об.SLUCH[0][Эл.Имя], , "SLUCH ");
КонецЦикла;

Процедура ЗаполнитьПоле(Поле, Значение, РазрешитьДобавить = Ложь, Коментарий = "")
    Если Значение <> Неопределено Тогда
        Если ТипЗнч(Поле) = Тип("Строка") Или ТипЗнч(Поле) = Тип("Число") Или ТипЗнч(Поле) = Тип("Дата") Тогда
            Поле = Значение;
        Иначе
            Мд = Метаданные.НайтиПоТипу(ТипЗнч(Поле));
            Спр = Справочники[Мд.Имя];
            Если СокрЛП(Значение) <> "" Тогда
                Если ТипЗнч(Поле.Код) = Тип("Строка") Тогда
                    Поле = Спр.НайтиПоКоду(Строка(Значение));
                ИначеЕсли ТипЗнч(Поле.Код) = Тип("Число") Тогда
                    Попытка 
                        Поле = Спр.НайтиПоКоду(Число(Значение));
                    Исключение
                    КонецПопытки;
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

Выясним главные проблемы данного подхода:

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

Способ 1. Кэширование ссылок и предварительное получение данных

Рассмотрим первый метод оптимизации — отказ от поиска элементов справочника непосредственно в момент обхода строки. Вместо вызова НайтиПоКоду() в цикле мы можем один раз собрать все уникальные коды из XML, получить ссылки единым запросом и сохранить их в Соответствие.

Проанализируем порядок действий:

  1. Обходим XML и собираем массивы кодов для каждого справочника.
  2. Выполняем один запрос к базе данных с использованием конструкции В (&МассивКодов) для каждого типа справочника.
  3. Заполняем коллекцию Соответствие, где ключом выступает код из XML, а значением — полученная ссылка на элемент справочника.
  4. При заполнении строк табличной части берем готовую ссылку из кэша мгновенно по ключу.

Посмотрим на пример реализации алгоритма кэширования кодов:


// Инициализация кэша ссылок
КэшКодов = Новый Соответствие; // Ключ - Код, Значение - Ссылка

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

Запрос.УстановитьПараметр("МассивКодов", МассивПрочитанныхКодов);
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    КэшКодов.Вставить(СокрЛП(Выборка.Код), Выборка.Ссылка);
КонецЦикла;

Способ 2. Предварительная подготовка структуры метаданных

Для исключения постоянных обращений к глобальному контексту метаданных рекомендуется зафиксировать типы и свойства реквизитов табличной части до начала загрузки данных. Создадим описание структуры колонок один раз перед циклом обработки.


// Сформируем описание типов реквизитов табличной части
КартаРеквизитов = Новый Структура;

ТаблицаДокумента = Объект.SLUCH;
МетаданныеТЧ = Метаданные.Документы[Объект.Метаданные().Имя].ТабличныеЧасти.SLUCH;

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

Способ 3. Загрузка данных через промежуточную Таблицу Значений

Построчное добавление элементов через метод Объект.SLUCH.Добавить() заставляет платформу пересчитывать индексы и обновлять коллекцию строк на каждом шаге. Чтобы ускорить процесс, заполним оперативную ТаблицуЗначений в памяти, после чего загрузим её в документ одной операцией.

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


// Выгружаем структуру ТЧ в новую Таблицу Значений
ТЗЗагрузки = Объект.SLUCH.ВыгрузитьКолонки();

// Заполняем строку таблицы значений
Для Каждого СтрокаXML Из Об.SLUCH Цикл
    НоваяСтрока = ТЗЗагрузки.Добавить();
    
    Для Каждого Свойство Из СтрокаXML.Свойства() Цикл
        ИмяПоля = Свойство.Имя;
        ЗначениеXML = СтрокаXML[ИмяПоля];
        
        Если ЗначениеXML = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        // Получаем ссылку из заранее подготовленного кэша
        НайденнаяСсылка = КэшКодов[СокрЛП(ЗначениеXML)];
        Если НайденнаяСсылка <> Неопределено Тогда
            НоваяСтрока[ИмяПоля] = НайденнаяСсылка;
        Иначе
            НоваяСтрока[ИмяПоля] = ЗначениеXML;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

// Однократная загрузка всех подготовленных строк в документ
Объект.SLUCH.Загрузить(ТЗЗагрузки);

Рекомендации по повышению производительности

Сводные правила для ускорения обработки XML при работе с XDTO:

  1. Выполняйте обработку на сервере: Код загрузки и поиска должен вызывать контекст &НаСервере без передач промежуточных данных на клиент.
  2. Заменяйте НайтиПоКоду на Запросы: Никогда не используйте поиск элементов по коду или наименованию внутри циклов большой размерности.
  3. Загружайте данные пачкой: Используйте метод Загрузить() вместо построчного добавления через Добавить().
  4. Кэшируйте функции с повторным использованием: Для получения часто встречающихся ссылок можно вынести логику в общий модуль с включенным повторным использованием возвращаемых значений.
← На главную