В практике разработчика 1С часто возникает задача загрузки данных из внешних источников, и Excel является самым популярным из них. Однако при попытке обработать файлы, содержащие сотни тысяч строк, система часто выдает критическую ошибку Недостаточно памяти. В рамках данной статьи мы разберем, почему это происходит, проанализируем различные методы работы с файлами и выработаем оптимальную стратегию загрузки, которая позволит стабильно обрабатывать огромные массивы данных без падения системы.
Основная причина ошибки Недостаточно памяти кроется в архитектуре взаимодействия 1С с COM-объектами (Excel.Application). При использовании COM-соединения операционная система запускает полноценный процесс Excel, который потребляет значительные ресурсы. Если файл содержит 100 000 строк и более, объем памяти, выделяемой под дерево объектов Excel, может превысить лимит для 32-битного процесса (около 2-3 ГБ). Даже в 64-битных системах неэффективное чтение каждой ячейки по отдельности приводит к фрагментации памяти и резкому снижению производительности.
Рассмотрим подробнее, как можно избежать этой ситуации, используя встроенные инструменты платформы 1С:Предприятие 8.3.
Начиная с версии 8.3.6, платформа 1С позволяет открывать файлы .xlsx напрямую, не обращаясь к установленным в системе офисным приложениям. Это самый быстрый и экономный по памяти способ. Разберем по шагам, как реализовать этот алгоритм.
Проанализируем пример кода для загрузки данных через ТабличныйДокумент:
&НаСервере
Процедура ЗагрузитьФайлНаСервере(ПутьКФайлу)
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка при чтении файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
КоличествоСтрок = ТабДок.ВысотаТаблицы;
КоличествоКолонок = ТабДок.ШиринаТаблицы;
Для НомерСтроки = 2 По КоличествоСтрок Цикл // Начинаем со 2-й строки, пропуская заголовок
ОбъектДанных = Новый Структура;
ОбъектДанных.Вставить("Артикул", ТабДок.Область(НомерСтроки, 1).Текст);
ОбъектДанных.Вставить("Наименование", ТабДок.Область(НомерСтроки, 2).Текст);
ОбъектДанных.Вставить("Цена", ТабДок.Область(НомерСтроки, 3).Текст);
// Обработка полученных данных
ОбработатьСтрокуЗагрузки(ОбъектДанных);
КонецЦикла;
КонецПроцедуры
Этот метод позволяет прочитать файл гораздо быстрее, чем через COM, так как ТабличныйДокумент оптимизирован для работы внутри памяти сервера 1С. Однако стоит помнить, что при экстремально больших объемах (например, 500 000 строк) сам объект ТабДок также может занять много места. В таких случаях мы рекомендуем использовать разделение процесса.
Если памяти все равно не хватает, проанализируем ситуацию с точки зрения жизненного цикла процессов. При выполнении кода в контексте основного сеанса пользователя память может не возвращаться операционной системе до полного закрытия программы. Вынесение загрузки в ФоновоеЗадание позволяет системе полностью очистить ресурсы после завершения задачи.
Посмотрим на пример запуска загрузки в фоне:
&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(АдресФайлаВоВременномХранилище);
ФоновыеЗадания.Выполнить("МодульЗагрузки.ЗагрузитьДанныеИзExcel", ПараметрыЗадания);
Сообщить("Загрузка запущена в фоновом режиме.");
КонецПроцедуры
В этом случае вся "тяжелая" работа ложится на отдельный процесс rphost. После того как задание отработает, менеджер фоновых заданий уничтожит поток, и вся выделенная память ТабличногоДокумента гарантированно высвободится.
Если файл Excel имеет огромный размер, а формат данных позволяет обращаться к нему как к базе данных, мы можем использовать технологию ADO (ActiveX Data Objects). Это позволяет не загружать весь файл в память 1С целиком, а читать его построчно (курсором).
Разберем пример реализации через ADODB.Connection:
Connection = Новый COMОбъект("ADODB.Connection");
ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + ПутьКФайлу + ";Extended Properties=""Excel 12.0 Xml;HDR=YES"";";
Connection.Open(ConnectionString);
Recordset = Новый COMОбъект("ADODB.Recordset");
Recordset.Open("SELECT * FROM [Лист1$]", Connection);
Пока НЕ Recordset.EOF Цикл
Артикул = Recordset.Fields.Item("Артикул").Value;
// Логика сохранения данных
Recordset.MoveNext();
КонецЦикла;
Recordset.Close();
Connection.Close();
Важным преимуществом этого метода является то, что 1С не создает тяжелых визуальных объектов. Мы работаем только со значениями, что критически важно для экономии оперативной памяти.
Помимо выбора метода чтения, важно правильно организовать саму запись данных в 1С. Рассмотрим основные правила, которые помогут нам сохранить стабильность системы:
Неопределено. Это помогает сборщику мусора 1С понять, что объект больше не используется.Проанализируем пример оптимизированного поиска:
// Плохо: поиск в каждой итерации
Для Каждого СтрокаТЗ Из ДанныеExcel Цикл
СсылкаНоменклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаТЗ.Наименование);
КонецЦикла;
// Хорошо: предварительная загрузка в соответствие
Запрос = Новый Запрос("ВЫБРАТЬ Наименование, Ссылка ИЗ Справочник.Номенклатура");
ТЗ_Номенклатура = Запрос.Выполнить().Выгрузить();
СоответствиеНоменклатуры = Новый Соответствие;
Для Каждого Стр Из ТЗ_Номенклатура Цикл
СоответствиеНоменклатуры.Вставить(Стр.Наименование, Стр.Ссылка);
КонецЦикла;
Для Каждого СтрокаТЗ Из ДанныеExcel Цикл
СсылкаНоменклатура = СоответствиеНоменклатуры.Получить(СтрокаТЗ.Наименование);
КонецЦикла;
Мы выяснили, что основной причиной ошибки Недостаточно памяти является избыточное использование ресурсов процессом Excel через COM-соединение. Для решения проблемы мы рекомендуем:
ТабДок.Прочитать() для файлов среднего размера.Соблюдение этих правил позволит вам создавать надежные механизмы интеграции, способные обрабатывать любые объемы данных без сбоев и потери производительности.