Как загрузить и обработать огромный DBF-файл в 1С без ошибки «Недостаточно памяти»?

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

При работе с внешними источниками данных разработчики 1С часто сталкиваются с необходимостью импорта или сверки информации из файлов формата DBF. Если размер файла составляет несколько мегабайт, стандартные методы платформы справляются с задачей за секунды. Однако, когда речь заходит о действительно больших объемах — например, файлах размером более 1.4 ГБ, содержащих свыше 120 миллионов строк, — простая попытка прочитать данные «в лоб» и загрузить их в массив или таблицу значений неизбежно приводит к аварийному завершению работы системы с ошибкой «Недостаточно памяти».

В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем ошибки в архитектуре кода и рассмотрим эффективные практические решения, которые позволят вам быстро и стабильно обрабатывать гигантские массивы данных, сохраняя стабильность сервера 1С.

Почему происходит переполнение оперативной памяти?

Перед тем как перейти к написанию кода, выясним причину, по которой сервер приложений 1С (процесс rphost) или клиентский сеанс начинают лавинообразно потреблять оперативную память. В исходном коде автора темы прослеживаются три критические архитектурные ошибки:

  1. Передача контекста формы между клиентом и сервером. Процедура чтения выполняется на клиенте (&НаКлиенте), а процедура сравнения — на сервере (&НаСервере). При каждом вызове серверного метода в цикле платформа 1С вынуждена сериализовать и передавать по сети весь контекст управляемой формы, включая тяжелые таблицы значений (такие как Реквизит1, Реквизит2 и другие). При миллионных объемах данных это мгновенно забивает ОЗУ.
  2. Попытка удержать весь объем данных в оперативной памяти. Создание таблиц значений на 120 млн строк физически невозможно в рамках доступной оперативной памяти одного процесса. Память тратится не только на сами данные, но и на служебные структуры 1С (индексы, типы данных колонок).
  3. Создание временных таблиц в запросе в бесконечном цикле. Передача больших таблиц значений в качестве параметров запроса заставляет СУБД создавать физические таблицы в базе данных tempdb. Если делать это циклически для каждой порции данных, СУБД и менеджер временных таблиц 1С не будут успевать освобождать выделенные ресурсы.

Рассмотрим подробнее три основных способа решения этой проблемы, начиная с оптимизации стандартного механизма XBase и заканчивая альтернативными системными архитектурами.

Решение №1: Организация пакетного (порционного) чтения через XBase

Самый простой способ избежать переполнения памяти — читать файл небольшими порциями (пакетами), обрабатывать каждую порцию независимо и сразу же очищать память перед чтением следующей. Объект 1С XBase не загружает весь DBF-файл в оперативную память целиком, а предоставляет последовательный доступ к физическим записям на диске. Нам нужно лишь научиться вовремя останавливать чтение и запоминать позицию курсора.

Для этого мы будем использовать два ключевых свойства объекта XBase:

Разберем по шагам правильную реализацию этого алгоритма. Весь процесс чтения и сравнения мы перенесем на сервер без передачи контекста формы (используя директиву &НаСервереБезКонтекста), чтобы исключить накладные расходы на сериализацию.

Посмотрим на пример кода, реализующий этот подход:


&НаСервереБезКонтекста
Процедура ОбработатьОгромныйФайлНаСервере(ПутьКФайлу, РазмерПартии)
    
    // Инициализируем объект для работы с DBF
    ФайлDBF = Новый XBase;
    Попытка
        ФайлDBF.ОткрытьФайл(ПутьКФайлу, , Истина); // Открываем только для чтения
    Исключение
        ЗаписатьЛогСобытий("Не удалось открыть файл: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    
    // Получаем общее количество записей для контроля процесса
    ВсегоЗаписей = ФайлDBF.КоличествоЗаписей();
    ТекущийНомерЗаписи = 1;
    
    // Подготавливаем структуру для временного хранения пакета данных
    ТаблицаПакета = СоздатьТаблицуДляПакета();
    
    Пока ТекущийНомерЗаписи <= ВсегоЗаписей Цикл
        
        // Позиционируем курсор на начало очередной партии
        ФайлDBF.Перейти(ТекущийНомерЗаписи);
        
        СчетчикПартии = 0;
        ТаблицаПакета.Очистить();
        
        // Считываем порцию данных в таблицу значений
        Пока НЕ ФайлDBF.ВКонце() И СчетчикПартии < РазмерПартии Цикл
            
            НоваяСтрока = ТаблицаПакета.Добавить();
            // Формируем уникальный ключ для сопоставления
            СерияНомер = СокрЛП(ФайлDBF.Series) + СокрЛП(ФайлDBF.Number);
            НоваяСтрока.СерияНомер = СтрЗаменить(СерияНомер, " ", "");
            
            ФайлDBF.Следующая();
            СчетчикПартии = СчетчикПартии + 1;
            
        КонецЦикла;
        
        // Запоминаем позицию, на которой остановились
        ТекущийНомерЗаписи = ФайлDBF.НомерЗаписи;
        
        // Передаем накопленный пакет на сравнение с базой данных
        СверитьПакетДанныхСБазой(ТаблицаПакета);
        
        // Принудительно очищаем память
        ТаблицаПакета.Очистить();
        
        // Позволяем операционной системе и сборщику мусора 1С высвободить ресурсы
        Если ТекущийНомерЗаписи % (РазмерПартии * 10) = 0 Тогда
            #Если Клиент Тогда
                ОбработкаПрерыванияПользователя();
            #КонецЕсли
        КонецЕсли;
        
    КонецЦикла;
    
    ФайлDBF.ЗакрытьФайл();
    
КонецПроцедуры

В этом примере параметр РазмерПартии рекомендуется устанавливать в диапазоне от 50 000 до 100 000 записей. Это оптимальный баланс между скоростью работы СУБД и потреблением оперативной памяти.

Решение №2: Использование объекта «Соответствие» для мгновенного сравнения в памяти

Если для каждого прочитанного пакета из 100 000 строк вы будете выполнять классический запрос СУБД с передачей таблицы значений в качестве параметра, это все равно может сильно нагрузить систему. Проанализируем ситуацию: нам нужно сравнить данные файла с существующими записями в базе данных 1С.

Вместо постоянных запросов к СУБД гораздо эффективнее один раз выгрузить ключевые данные из базы 1С (например, действующие паспорта абонентов) в оперативную память сервера в виде объекта Соответствие. Поиск по ключу в объекте Соответствие (который реализован в платформе как хеш-таблица) происходит практически мгновенно и не требует обращений к СУБД в цикле.

Рассмотрим, как это реализовать на практике:


&НаСервереБезКонтекста
Функция ПолучитьКартуПаспортовИзБазы(КодПодразделения)
    
    // Создаем хеш-таблицу для быстрого поиска
    КартаПаспортов = Новый Соответствие;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ПаспортныеДанные.ДокументСерия КАК Серия,
        |   ПаспортныеДанные.ДокументНомер КАК Номер,
        |   Абоненты.Ссылка КАК Абонент
        |ИЗ
        |   Справочник.Абоненты КАК Абоненты
        |   ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ПаспортныеДанныеФизЛиц.СрезПоследних КАК ПаспортныеДанные
        |       ПО Абоненты.ВладелецЛС = ПаспортныеДанные.ФизЛицо
        |ГДЕ
        |   Абоненты.Подразделения = &Подразделения";
        
    Запрос.УстановитьПараметр("Подразделения", КодПодразделения);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Формируем уникальный ключ поиска
        КлючСвязи = СокрЛП(Выборка.Серия) + СокрЛП(Выборка.Номер);
        КлючСвязи = СтрЗаменить(КлючСвязи, " ", "");
        
        // Записываем в соответствие: Ключ -> Ссылка на абонента
        КартаПаспортов.Вставить(КлючСвязи, Выборка.Абонент);
    КонецЦикла;
    
    Возврат КартаПаспортов;
    
КонецФункции

Теперь при чтении очередной порции из DBF-файла нам не нужно делать запросы. Мы просто проверяем наличие ключа в нашей карте паспортов прямо в памяти сервера:


// Внутри цикла чтения файла DBF:
Пока НЕ ФайлDBF.ВКонце() И СчетчикПартии < РазмерПартии Цикл
    
    СерияНомер = СокрЛП(ФайлDBF.Series) + СокрЛП(ФайлDBF.Number);
    КлючПоиска = СтрЗаменить(СерияНомер, " ", "");
    
    // Ищем совпадение в подготовленном соответствии
    НайденныйАбонент = КартаПаспортов.Получить(КлючПоиска);
    
    Если НайденныйАбонент <> Неопределено Тогда
        // Совпадение найдено! Выполняем бизнес-логику (например, записываем в лог или ТЗ результатов)
        РегистрироватьСовпадение(НайденныйАбонент, КлючПоиска);
    КонецЕсли;
    
    ФайлDBF.Следующая();
    СчетчикПартии = СчетчикПартии + 1;
    
КонецЦикла;

Этот подход позволяет обрабатывать миллионы записей в минуту, так как вся работа по сопоставлению происходит в оперативной памяти без накладных расходов на обращения к диску и СУБД.

Решение №3: Подключение DBF как «Внешнего источника данных» (ВИД)

Если вы хотите полностью отказаться от написания сложного кода обхода файлов и доверить всю работу механизмам платформы и СУБД, идеальным решением станет использование механизма Внешних источников данных в 1С 8.3.

Этот способ позволяет подключить каталог с вашими DBF-файлами непосредственно к схеме базы данных 1С через ODBC-драйвер. Платформа будет видеть файлы DBF как обычные таблицы базы данных, к которым можно обращаться с помощью стандартных запросов 1С.

Разберем этот процесс по шагам:

  1. Откройте конфигурацию в режиме Конфигуратора.
  2. Найдите ветку Внешние источники данных и добавьте новый источник (например, КаталогDBF).
  3. В свойствах источника укажите строку подключения (Connection String) с использованием провайдера Microsoft Access dBase Driver (или Microsoft Visual FoxPro Driver).
  4. Добавьте таблицу во внешний источник данных, указав имя вашего DBF-файла. Платформа автоматически считает структуру колонок.
  5. Обновите конфигурацию базы данных.

После этого вам больше не нужно писать циклы чтения. Сравнение 120 миллионов строк с данными вашей базы сводится к одному простому и элегантному запросу:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ПаспортаИзФайла.Series КАК Серия,
    |   ПаспортаИзФайла.Number КАК Номер,
    |   Абоненты.Ссылка КАК Абонент
    |ИЗ
    |   ВнешнийИсточникДанных.КаталогDBF.ТаблицаПаспортов КАК ПаспортаИзФайла
    |   ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Абоненты КАК Абоненты
    |       ПО (Абоненты.ДокументСерия + Абоненты.ДокументНомер) = (ПаспортаИзФайла.Series + ПаспортаИзФайла.Number)";

Результат = Запрос.Выполнить().Выгрузить();

Важное преимущество: СУБД (например, MS SQL Server или PostgreSQL) выполнит это соединение на уровне своих внутренних алгоритмов оптимизации. Платформа 1С вообще не будет считывать 120 миллионов строк в память приложения — СУБД выполнит сопоставление и вернет в 1С только те строки, по которым были найдены реальные совпадения. Это самый быстрый и профессиональный метод работы с большими файлами.

Советы по тонкой настройке и оптимизации памяти

Если вы все же решили использовать построчное чтение файлов, придерживайтесь следующих правил для предотвращения утечек памяти:

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

← На главную