Интеграции между базами данных с помощью формата XML явление постоянное, хорошо знакомое и часто описанное. Тем не менее однажды получил опыт, связанный с большими объёмами получаемых данных, при не самом мощном серверном техническом ресурсе. При всём знакомстве с методикой обмена через XML, пришлось отдельно подумать над тем, как не переживать за объёмы получаемых сведений и уровень технических средств. Как-то стало привычкой и описываемую методику применяю практически всегда, на всякий случай, чтобы не сильно "удивлять" сервер и не раздражать "подвисающих" пользователей.
Итак, классическая схема, классическая на уровне курсовой работы студента, конечно. Есть содержимое XML определенной структуры, нам эта структура известна, понятна и под неё мы создаём пакет XDTO. Получаем содержимое XML, загоняем его в объёкт чтения, этот объект чтения читаем Фабрикой и вот он, желаемый объект XDTO, который можно превращать в таблицу значений, например. Всё понятно, всё работает - проверено многократно. До тех пор, пока не прилетит содержимое размером в десятки или сотни мегабайт (почему бы нет?) Или, ещё проще, файл огромный, но в нём нужен всего-лишь небольшой фрагмент. И получится следующее. Мы создадим неподъёмный по объёму объектXDTO, который должен порождать не менее объёмную таблицу значений, причём, оба этих "пухлых" объекта будут существовать одновременно. На каждом сервере можно повесить табличку "Памяти много не бывает!") Вот такой ситуации всегда хочется, нужно и можно избежать! Поделюсь опытом, вдруг кому-то пригодится.
Живой пример (реальная задача). Сразу оговорюсь, совсем гигантских объёмов здесь не проявилось, но речь поёдёт о типовых конфигурациях, участвующих в обмене, и хорошо знакомых объектах конфигурации, будет вполне наглядно. Итак, задача звучит так "в ЕРП справочник номенклатуры содержит сведения о весовых характеристиках номенклатуры. Надо эти значения перенести в базу БП, в аналогичный справочник". Несложная, но вполне жизненная задачка. С выгрузкой из ЕРП проблем не возникает. Только оговорюсь по процедуре создания Фабрики. Во внешних обработках, где необходимо создавать Фабрику по своей схеме, я предпочитаю использовать макет вида "двоичные данные". Макет содержит загруженный файл XSD. Из этого макета я создаю необходимую мне Фабрику (подробности - ниже). Файл выгрузки получился чуть меньше 10 мБайт. Забыл уточнить, я использую содержимое XML в формате FastInfoSet, т.к. в строковом представлении содержимое оказывается раз в пять объёмнее.
И теперь подробно, приступаем к чтению файла на стороне получения. Файл выбран и помещён на сервер. У нас есть двоичные данные файла, приступаем.
На стороне получения тоже внешняя обработка. В ней так же есть макет с двоичными данными схемы XSD.
Вот так я привык получать нужную мне фабрику из макета двоичных данных:
Функция СоздатьФабрикуПоСхемеИзМакета()
МакетСхемы = ЭтотОбъект.ПолучитьМакет("СхемаПриИмпорте");
ПотокЧтенияДД = МакетСхемы.ОткрытьПотокДляЧтения();
ОбъектЧтения = Новый ЧтениеXML;
ОбъектЧтения.ОткрытьПоток(ПотокЧтенияДД);
Построитель = Новый ПостроительDOM;
СхемаЧтения = Построитель.Прочитать(ОбъектЧтения);
Построитель = Новый ПостроительСхемXML;
СхемаЧтения = Построитель.СоздатьСхемуXML(СхемаЧтения.ЭлементДокумента);
НаборСхем = Новый НаборСхемXML;
НаборСхем.Добавить(СхемаЧтения);
МояФабрика = Новый ФабрикаXDTO(НаборСхем);
Возврат МояФабрика
КонецФункции
Следует описать - а что содержит схема, которой я читаю содержимое XML. Для наглядности переведу (импортирую) схему в конфигуратор, в пакет XDTO.

По привычной логике выгрузок-загрузок "Номенклатура" должна, вроде бы, быть элементом с минимальным количеством элементов "0" и максимальным "-1" (СписокXDTO). Но именно такой список и сделает содержимое при чтении слишком большим, мы же будем вынуждены прочесть весь список - сразу. В моём случае "Номенклатура" сделана типом. Это описание повторяющегося фрагмента содержимого XML. Теперь несложная задача - прочитать содержимое XML фрагментами (кусочками), точно попадая в начало и конец описанного в нашем типе XDTO элемента ("Номенклатура").
Пример в процедуре ниже:
Процедура ЗавершитьИмпортФайлаНаСервере(АдресХранения) Экспорт
СодержимоеДД = ПолучитьИзВременногоХранилища(АдресХранения);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("fis");
СодержимоеДД.Записать(ИмяВременногоФайла);
СодержимоеДД = Неопределено;
ПотокЧтения = ФайловыеПотоки.ОткрытьДляЧтения(ИмяВременногоФайла, 65356);
ОбъектЧтения = Новый ЧтениеFastInfoset;
ОбъектЧтения.ОткрытьПоток(ПотокЧтения);
ЗаписьФрагмента = Новый ЗаписьFastInfoset;
ВестиЗапись = Ложь;
СтруктураИсполнения = СтруктураИсполненияПриИмпортеФайла();
Пока ОбъектЧтения.Прочитать() Цикл
Если ОбъектЧтения.ИмеетИмя И ОбъектЧтения.ЛокальноеИмя = "Номенклатура" Тогда
Если ОбъектЧтения.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ВестиЗапись = Истина;
ПотокФрагмента = Новый ПотокВПамяти(1024);
ЗаписьФрагмента.ОткрытьПоток(ПотокФрагмента);
ИначеЕсли ОбъектЧтения.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
ЗаписьФрагмента.ЗаписатьТекущий(ОбъектЧтения);
ВестиЗапись = Ложь;
ЗаписьФрагмента.Закрыть();
ПотокФрагмента.Перейти(0, ПозицияВПотоке.Начало);
ПотокФрагмента = ПотокФрагмента.ПолучитьПотокТолькоДляЧтения();
ВнестиСтрокуВТаблицуПереноса(СтруктураИсполнения, ПотокФрагмента);
КонецЕсли;
КонецЕсли;
Если ВестиЗапись Тогда
ЗаписьФрагмента.ЗаписатьТекущий(ОбъектЧтения);
КонецЕсли;
КонецЦикла;
ОбъектЧтения.Закрыть();
ПотокЧтения.Закрыть();
Если СтруктураИсполнения.ТаблицаПереноса.Количество() > 0 Тогда
ТаблицаПереносаВоВременнуюТаблицуЗапроса(СтруктураИсполнения);
КонецЕсли;
ФайловаяСистема.УдалитьВременныйФайл(ИмяВременногоФайла);
Запрос = СтруктураИсполнения.Запрос;
СтруктураИсполнения = Неопределено;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураСпр.Ссылка КАК Ссылка,
| ТаблицаПереноса.Вес КАК Вес
|ИЗ
| Справочник.Номенклатура КАК НоменклатураСпр
| ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаПереносаВТ КАК ТаблицаПереноса
| ПО НоменклатураСпр.Код = ТаблицаПереноса.Код
| И (СОКРЛП(НоменклатураСпр.Артикул) <> """"
| И СОКРЛП(ТаблицаПереноса.Артикул) <> """"
| И СОКРЛП(НоменклатураСпр.Артикул) = СОКРЛП(ТаблицаПереноса.Артикул)
| ИЛИ ИСТИНА)
|ГДЕ
| НЕ ТаблицаПереноса.Вес ЕСТЬ NULL
| И НЕ НоменклатураСпр.ПометкаУдаления
| И НЕ НоменклатураСпр.ЭтоГруппа";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Попытка
ОбъектНоменклатуры = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатуры.Вес = Выборка.Вес;
ОбъектНоменклатуры.Записать();
Исключение
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Кратко прокомментирую прилагаемый код, он совсем несложен. Двоичные данные сохраняются во временный файл, из которого в свою очередь создаётся файловый поток чтения. Небольшая оговорка, переменную, содержащую двоичные данные, я "обнуляю". Эта привычка возникла ещё в 90-е, но я считаю её полезной. Нет необходимости занимать память уже ненужными данными, особенно - объёмными.
Перед чтением создаю набор необходимых объектов и переменных, помещая их в одну структуру "СтруктураИсполнения":
Функция СоздатьПустуюТаблицуПереноса()
ТаблицаПереноса = Новый ТаблицаЗначений;
КоллекцияКолонок = ТаблицаПереноса.Колонки;
КоллекцияКолонок.Добавить("Код", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(11, ДопустимаяДлина.Переменная)));
КоллекцияКолонок.Добавить("Артикул", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50, ДопустимаяДлина.Переменная)));
КоллекцияКолонок.Добавить("Вес", Новый ОписаниеТипов("Число" , Новый КвалификаторыЧисла(15, 3, ДопустимыйЗнак.Неотрицательный)));
Возврат ТаблицаПереноса;
КонецФункции
Функция СтруктураИсполненияПриИмпортеФайла()
СтруктураИсполнения = Новый Структура;
СтруктураИсполнения.Вставить("МояФабрика", СоздатьФабрикуПоСхемеИзМакета());
СтруктураИсполнения.Вставить("СоздаваемыйТип", СтруктураИсполнения.МояФабрика.Тип("http://www.ToolsWorld/Export/ERPtoAccaunting", "Номенклатура"));
СтруктураИсполнения.Вставить("ТаблицаПереноса", СоздатьПустуюТаблицуПереноса());
СтруктураИсполнения.Вставить("ЧтениеФрагмента", Новый ЧтениеFastInfoset);
СтруктураИсполнения.Вставить("ДобавлятьВТ", Ложь);
Запрос = Новый Запрос(
"ВЫБРАТЬ
|ТаблицаПереноса.Код КАК Код,
|ТаблицаПереноса.Артикул КАК Артикул,
|ТаблицаПереноса.Вес КАК Вес
|ПОМЕСТИТЬ ТаблицаПереносаВТ
|ИЗ
| &ТаблицаПереноса КАК ТаблицаПереноса");
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
СтруктураИсполнения.Вставить("Запрос", Запрос);
Возврат СтруктураИсполнения;
КонецФункции
Опишу значения структуры:
"МояФабрика" и так понятно - почему и зачем.
"СоздаваемыйТип" - это типXDTO, позволяющий создавать соответствующий объект, а в структуре он, чтобы не создавать его при вводе каждой строки.
"ТаблицаПереноса" - это таблица значений, служащая для промежуточного накопления определённого набора строк. Эта таблица никогда не будет большой!
"ЧтениеФрагмента" - понятно для чего, в этом объекте будет читаться "кусочек", полученный из основного потока чтения.
"ДобавлятьВТ". Здесь подробнее. Цель применяемого решения - избежать создания громоздких объектов в памяти. Для этого создается таблица переноса (таблица значений) и запрос с задействованным менеджером временных таблиц. По мере заполнения таблицы переноса заданным количеством строк производится выполнение запроса и перемещение накопленных данных чтения во временную таблицу. При этом в первой итерации чтения запрос использует директиву "ПОМЕСТИТЬ", а в последующих итерациях установится директива "ДОБАВИТЬ".
"Запрос" - выше уже описал назначение этого объекта. Далее - читаем основное содержимое.
Файл читается в потоке построчно, отнимая при таком чтении минимум технического ресурса.
При нахождении нужного элемента, это определяется по его локальному имени, начинается либо запись фрагмента, либо окончание записи фрагмента. Фрагментом служит отдельный объект записи вида "ЗаписьFastInfoSet". Запись ведется в "ПотокВПамяти". Размер буфера потока несложно прикинуть, понимая примерный объём потенциального фрагмента.
В момент прохождения (чтения) узла с требуемым именем ("Номенклатура") и типом узла "КонецЭлемента" этот узел записывается во "фрагмент". После чего поток записи преобразуется в поток чтения. Обязательно не забыть - переместить позицию в потоке на начало, т.к. после записи мы находимся в конечной точке потока. Сформированный поток чтения фрагмента передаётся в обработку Фабрикой.
Процедура ТаблицаПереносаВоВременнуюТаблицуЗапроса(СтруктураИсполнения)
Если СтруктураИсполнения.ДобавлятьВТ Тогда
СхемаSQL = Новый СхемаЗапроса;
СхемаSQL.УстановитьТекстЗапроса(СтруктураИсполнения.Запрос.Текст);
ПакетСхемы = СхемаSQL.ПакетЗапросов.Получить(0);
ПакетСхемы.ТаблицаДляПомещения = "";
ПакетСхемы.ТаблицаДляДобавления = "ТаблицаПереносаВТ";
СтруктураИсполнения.Запрос.Текст = СхемаSQL.ПолучитьТекстЗапроса();
КонецЕсли;
СтруктураИсполнения.Запрос.УстановитьПараметр("ТаблицаПереноса", СтруктураИсполнения.ТаблицаПереноса);
СтруктураИсполнения.Запрос.Выполнить();
СтруктураИсполнения.ТаблицаПереноса.Очистить();
СтруктураИсполнения.Вставить("ДобавлятьВТ", Истина);
КонецПроцедуры
Процедура ВнестиСтрокуВТаблицуПереноса(СтруктураИсполнения, ПотокФрагмента)
СтруктураИсполнения.ЧтениеФрагмента.ОткрытьПоток(ПотокФрагмента);
ОбъектСтроки = СтруктураИсполнения.МояФабрика.ПрочитатьXML(СтруктураИсполнения.ЧтениеФрагмента, СтруктураИсполнения.СоздаваемыйТип);
СтруктураИсполнения.ЧтениеФрагмента.Закрыть();
ПотокФрагмента.Закрыть();
НоваяСтрока = СтруктураИсполнения.ТаблицаПереноса.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ОбъектСтроки);
Если СтруктураИсполнения.ТаблицаПереноса.Количество() >= 500 Тогда
ТаблицаПереносаВоВременнуюТаблицуЗапроса(СтруктураИсполнения);
КонецЕсли;
КонецПроцедуры
В прилагаемом примере каждый читаемый фрагмент образует строку в таблице значений. При достижении количества строк (в данном случае 500) выполняется процедура переноса данных из таблицы значений во временную таблицу запроса, после чего таблица значений очищается. Перед выполнение запроса анализируется - создавать временную таблицу или дополнять уже существующую. Текст запроса соответственно изменяется с использованием "СхемаЗапроса".
Собственно, на этом заканчивается то, что позволяет избежать создания избыточно громоздких объектов. По итогу в нижних строках кода оказывается запрос, содержащий временную таблицу. Можно задавать новый текст запроса и уже распоряжаться данными из временной таблицы, что у меня и происходит.
В таком режиме работы я уже не переживаю за потенциальный размер читаемого содержимого XML. Единственное место, где на короткое время может возникнуть избыточно большое значение, это получение двоичных данных из хранилища. Если кто-то придумает - как этого избежать - поделитесь идеями!
Вступайте в нашу телеграмм-группу Инфостарт
