Как ускорить массовое обновление данных в DBF-файлах из 1С 7.7

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При работе с внешними базами данных в формате DBF из среды 1С:Предприятие 7.7 программисты часто сталкиваются с критическим падением производительности. Ситуация, когда обновление 150 000 записей занимает полтора часа, является недопустимой для бизнес-процессов. Однако, используя правильные механизмы драйвера VFPOLEDB и объектной модели ADO, это время можно сократить до нескольких десятков секунд. Разберем детально, почему возникают задержки и как оптимизировать код для достижения максимальной скорости.

Анализ причины низкой производительности

Основная причина медленной работы, которую мы видим в исходном коде автора, заключается в выполнении одиночных SQL-запросов UPDATE внутри цикла. Когда мы вызываем Запрос.ВыполнитьИнструкцию() или RecordSet.Update() для каждой строки, происходит следующее:

  1. 1С передает команду OLEDB-провайдеру.
  2. Провайдер открывает файл (если он не заблокирован монопольно).
  3. Производится поиск нужной записи.
  4. Данные записываются на диск.
  5. Самое важное: после каждой операции записи драйвер принудительно обновляет индексные файлы (CDX) и сбрасывает буфер на физический носитель.

Этот процесс повторяется 150 000 раз, что и создает огромные задержки. Рассмотрим способы решения этой задачи.

Решение 1: Использование транзакций (самый быстрый способ)

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

Проанализируем, как изменить код при использовании ADODB.Connection:


ОлеДБ = СоздатьОбъект("ADODB.Connection");
Соединение = "Provider=VFPOLEDB.1;Data Source=" + База + ";Mode=ReadWrite;";
ОлеДБ.Open(Соединение);

// Включаем транзакцию перед началом цикла
ОлеДБ.BeginTrans();

Пока RecordSet.EOF() = 0 Цикл
    RecordSet.Fields("II").Value = с;
    RecordSet.Update();
    с = с + 1;
    RecordSet.MoveNext();  
КонецЦикла;

// Фиксируем все изменения разом
ОлеДБ.CommitTrans();

Использование BeginTrans() и CommitTrans() позволяет сократить время обработки с часов до секунд, так как количество дисковых операций уменьшается в тысячи раз.

Решение 2: Пакетное обновление (UpdateBatch) в ADO

Если работа ведется через RecordSet, можно использовать режим оптимистической пакетной блокировки. Это позволяет изменять записи локально в памяти объекта RecordSet, а затем отправить весь пакет изменений одной командой.

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

  1. Установим CursorLocation = 3 (adUseClient) — это обязательное условие для пакетного обновления.
  2. Установим LockType = 4 (adLockBatchOptimistic).
  3. После цикла вызовем метод UpdateBatch().

RecordSet = СоздатьОбъект("ADODB.RecordSet"); 
RecordSet.ActiveConnection = ОлеДБ;
RecordSet.CursorLocation = 3; 
RecordSet.LockType = 4; // BatchOptimistic
RecordSet.Open("SELECT * FROM KL", ОлеДБ);

с = 1;
Пока RecordSet.EOF() = 0 Цикл
    RecordSet.Fields("II").Value = с;
    // Метод Update() здесь только помечает запись как измененную в памяти
    RecordSet.Update();
    с = с + 1;
    RecordSet.MoveNext();  
КонецЦикла;

// Отправляем все изменения в файл одной операцией
RecordSet.UpdateBatch();

Решение 3: Использование нативных команд FoxPro через ExecScript

Провайдер VFPOLEDB уникален тем, что позволяет выполнять не только SQL-запросы, но и стандартные команды встроенного языка Visual FoxPro. Команда REPLACE ALL работает значительно быстрее, чем SQL-команда UPDATE, так как она оптимизирована специально для движка DBF.

Рассмотрим ситуацию, когда нам нужно просто пронумеровать записи. Вместо того чтобы перебирать их в 1С, мы можем дать команду самому движку базы данных использовать встроенную функцию RECNO() (номер записи):


// Создаем объект для выполнения команд
Запрос = oledb.СоздатьКоманду();

// Используем ExecScript для выполнения нативного кода FoxPro
// RECNO() возвращает физический номер строки в таблице
ТекстЗапроса = "EXECSCRIPT('USE KL IN 0 EXCLUSIVE', 'REPLACE ALL II WITH RECNO() IN KL', 'USE IN KL')";
Запрос.ВыполнитьИнструкцию(ТекстЗапроса);

Этот метод будет работать практически мгновенно, так как исключается передача данных между 1С и OLEDB-провайдером для каждой строки.

Решение 4: Оптимизация индексов и подготовка запросов

Если задача требует сложной логики обновления, которую нельзя заменить одной командой REPLACE, обратим внимание на подготовку SQL-запросов (Prepared Statements). В 1С 7.7 при использовании OLEDB это реализуется через метод Prepare().

Проанализируем пример с параметрами:


Запрос = oledb.СоздатьКоманду();
ТекстЗапроса = "UPDATE KL SET II = ? WHERE ID_FIELD = ?";
Запрос.УстановитьТекстовыйПараметр("счетчик", 0); // Резервируем тип
Запрос.УстановитьТекстовыйПараметр("ид", 0);

// Подготавливаем план запроса один раз
Запрос.Подготовить(); 

Для с = 1 По тз.КоличествоСтрок() Цикл
    Запрос.УстановитьПараметр(1, с);
    Запрос.УстановитьПараметр(2, тз.ПолучитьЗначение(с, "ID"));
    Запрос.ВыполнитьИнструкцию();
КонецЦикла;

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

  1. Удалить индексный файл (CDX).
  2. Выполнить обновление данных.
  3. Пересоздать индекс заново одной командой INDEX ON ....

Сводные рекомендации

Для достижения наилучшего результата (как у автора темы — 34 секунды вместо 1.5 часов) мы рекомендуем следовать следующим правилам:

Применяя эти методики, вы сможете эффективно обрабатывать миллионы записей в DBF-файлах, используя привычные инструменты 1С 7.7.

← На главную