Рассмотрим распространенную и технически сложную задачу: у нас есть огромный XML-файл (вплоть до нескольких миллионов строк), содержащий список услуг. Нам нужно прочитать этот файл, найти соответствующие услуги в справочнике 1С по их строковым наименованиям и записать найденные ссылки в базу данных. Проанализируем, как сделать это максимально эффективно, избегая нехватки оперативной памяти и зависания сервера на долгие часы.
Часто программисты прибегают к самому очевидному, но крайне неэффективному методу — поиску ссылок непосредственно во время чтения файла. Выглядит это примерно так:
Пока ЧитаемХМЛ.Прочитать() Цикл
Если ЧитаемХМЛ.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧитаемХМЛ.Имя = "Услуга" Тогда
НаименованиеИзФайла = ЧитаемХМЛ.Значение;
// КРИТИЧЕСКАЯ ОШИБКА: Запрос к БД в цикле
НайденнаяУслуга = Справочники.Услуги.НайтиПоНаименованию(НаименованиеИзФайла);
КонецЕсли;
КонецЦикла;
Разберем, почему так делать категорически нельзя. Метод НайтиПоНаименованию под капотом выполняет полноценный SQL-запрос к базе данных. Если в вашем файле 6 миллионов строк, платформа выполнит 6 миллионов отдельных запросов. Это приведет к колоссальной нагрузке на СУБД и растянет процесс загрузки на многие сутки. Кроме того, не стоит забывать, что по умолчанию данный метод ищет совпадение по началу строки, так как параметр ТочноеСоответствие установлен в значение Ложь. Это может привести к ошибочному связыванию данных.
При работе с файлами, содержащими миллионы строк, мы обязаны правильно управлять оперативной памятью сервера. Если попытаться загрузить такой файл целиком через объект ДокументDOM или в XDTO-пакет, оперативная память будет переполнена, и рабочий процесс 1С аварийно завершится.
Для огромных объемов данных используем строго последовательное (потоковое) чтение через объект ЧтениеXML. Он считывает файл узел за узлом, продвигаясь вперед, и вообще не загружает всю структуру документа в память платформы.
Чтобы избежать запросов в цикле, нам необходимо предварительно получить данные из базы и кэшировать их в оперативной памяти. На форумах часто спорят, какую коллекцию использовать: ТаблицаЗначений или Соответствие.
Посмотрим на практические замеры производительности: при поиске по коллекции из более чем 120 000 элементов Соответствие работает как минимум в 2 раза быстрее, чем обычная ТаблицаЗначений. Это объясняется тем, что Соответствие использует алгоритмы хэш-таблиц, обеспечивая практически мгновенный доступ к значению по ключу.
Если же логика вашего алгоритма требует использования именно ТаблицаЗначений (например, нужен сложный поиск сразу по нескольким колонкам), мы обязаны ее проиндексировать. Добавление индекса колоссально ускоряет метод поиска:
ТЗ_Услуги.Индексы.Добавить("Наименование");
НайденнаяСтрока = ТЗ_Услуги.Найти(ИскомоеНаименование, "Наименование");
Выгружать весь справочник услуг в Соответствие до начала цикла — хорошая идея только для небольших баз. Если справочник содержит сотни тысяч элементов, мы зря забьем оперативную память данными, которые могут даже не встретиться в XML-файле. Правильный подход — порционное кэширование.
Разберем этот алгоритм по шагам:
ГДЕ Наименование В (&МассивНаименований).Соответствие (где ключ — это наименование, а значение — ссылка на элемент).При таком подходе оперативная память не засоряется лишними элементами, а количество обращений к СУБД сокращается в тысячи раз.
Создание или обновление миллионов элементов генерирует огромный поток команд записи. Если не управлять этим процессом, СУБД будет открывать и закрывать транзакцию для каждой отдельной строки. Это не только медленно, но и сильно раздувает журнал транзакций SQL-сервера.
Процесс обязательно нужно оборачивать в явные транзакции блоками. Посмотрим на пример:
СчетчикЗаписей = 0;
РазмерПорции = 5000;
НачатьТранзакцию();
Пока ЧитаемХМЛ.Прочитать() Цикл
// ... логика обработки и создания элементов ...
СчетчикЗаписей = СчетчикЗаписей + 1;
Если СчетчикЗаписей % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Такой подход кардинально снижает нагрузку на дисковую подсистему и ускоряет загрузку.
Важно отметить, что синхронизация баз данных по строковым наименованиям в высоконагруженных обменах считается архитектурной ошибкой. Наименования часто дублируются, в них случайно добавляются лишние пробелы, спецсимволы, или они просто изменяются пользователями.
Для надежного обмена в XML-файле должны передаваться уникальные идентификаторы (GUID), артикулы или коды. Выясним причину, почему GUID предпочтительнее: при получении ссылки по GUID метод платформы Справочники.Услуги.ПолучитьСсылку(Новый УникальныйИдентификатор(СтрокаGUID)) отрабатывает мгновенно. Платформе даже не нужно обращаться к физической таблице базы данных, чтобы сформировать пустую ссылку, что идеально подходит для массовой загрузки данных.
Если вы планируете записать миллионы найденных ссылок в табличную часть какого-либо справочника или документа, необходимо остановиться и пересмотреть архитектуру. Платформа 1С:Предприятие не предназначена для хранения миллионов строк в табличных частях ссылочных объектов.
Попытка открыть такой документ приведет к тому, что клиентское приложение "повиснет" на этапе отрисовки интерфейса. Архитектурно правильное решение для хранения огромных списков — это независимые Регистры Сведений. Запись в регистр следует производить через объект НаборЗаписей с обязательным включением свойства ОбменДанными.Загрузка = Истина, чтобы отключить лишние проверки и бизнес-логику при записи.
Если после внедрения всех вышеописанных оптимизаций скорость загрузки все еще недостаточна, проанализируем возможность многопоточной обработки. Если файл можно логически разбить на несколько независимых частей, имеет смысл задействовать ФоновыеЗадания.
Каждый фоновый поток будет читать свою часть файла, иметь собственный локальный кэш (соответствие) и управлять своими независимыми транзакциями. Это позволит утилизировать мощности сервера на 100% (задействовать все доступные ядра процессора) и сократить время загрузки с нескольких часов до нескольких минут.
Внедряя эти методики комплексно — от потокового чтения до порционного кэширования и транзакций — вы сможете создать отказоустойчивый и максимально быстрый алгоритм загрузки данных любой сложности в 1С.