При работе с внешними базами данных в формате DBF из среды 1С:Предприятие 7.7 программисты часто сталкиваются с критическим падением производительности. Ситуация, когда обновление 150 000 записей занимает полтора часа, является недопустимой для бизнес-процессов. Однако, используя правильные механизмы драйвера VFPOLEDB и объектной модели ADO, это время можно сократить до нескольких десятков секунд. Разберем детально, почему возникают задержки и как оптимизировать код для достижения максимальной скорости.
Основная причина медленной работы, которую мы видим в исходном коде автора, заключается в выполнении одиночных SQL-запросов UPDATE внутри цикла. Когда мы вызываем Запрос.ВыполнитьИнструкцию() или RecordSet.Update() для каждой строки, происходит следующее:
Этот процесс повторяется 150 000 раз, что и создает огромные задержки. Рассмотрим способы решения этой задачи.
Транзакции позволяют объединить тысячи операций записи в один логический блок. При открытии транзакции драйвер перестает сбрасывать данные на диск после каждой строки и накапливает изменения в оперативной памяти. Физическая запись и обновление индексов происходят один раз в момент завершения транзакции.
Проанализируем, как изменить код при использовании 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() позволяет сократить время обработки с часов до секунд, так как количество дисковых операций уменьшается в тысячи раз.
Если работа ведется через RecordSet, можно использовать режим оптимистической пакетной блокировки. Это позволяет изменять записи локально в памяти объекта RecordSet, а затем отправить весь пакет изменений одной командой.
Разберем этот метод по шагам:
CursorLocation = 3 (adUseClient) — это обязательное условие для пакетного обновления.LockType = 4 (adLockBatchOptimistic).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();
Провайдер 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-провайдером для каждой строки.
Если задача требует сложной логики обновления, которую нельзя заменить одной командой REPLACE, обратим внимание на подготовку SQL-запросов (Prepared Statements). В 1С 7.7 при использовании OLEDB это реализуется через метод Prepare().
Проанализируем пример с параметрами:
Запрос = oledb.СоздатьКоманду();
ТекстЗапроса = "UPDATE KL SET II = ? WHERE ID_FIELD = ?";
Запрос.УстановитьТекстовыйПараметр("счетчик", 0); // Резервируем тип
Запрос.УстановитьТекстовыйПараметр("ид", 0);
// Подготавливаем план запроса один раз
Запрос.Подготовить();
Для с = 1 По тз.КоличествоСтрок() Цикл
Запрос.УстановитьПараметр(1, с);
Запрос.УстановитьПараметр(2, тз.ПолучитьЗначение(с, "ID"));
Запрос.ВыполнитьИнструкцию();
КонецЦикла;
Важный совет: Если вы выполняете массовое обновление всей таблицы, наличие индексов по обновляемому полю (II в нашем примере) может замедлять работу, так как при каждом изменении индекс перестраивается. В некоторых случаях быстрее будет:
INDEX ON ....Для достижения наилучшего результата (как у автора темы — 34 секунды вместо 1.5 часов) мы рекомендуем следовать следующим правилам:
ADO или OLEDB.ExecScript для выполнения команд на языке FoxPro.WHERE, созданы индексы.CursorLocation = 3 для клиентских курсоров в ADO, чтобы снизить нагрузку на сетевой трафик при работе с файловыми базами.Применяя эти методики, вы сможете эффективно обрабатывать миллионы записей в DBF-файлах, используя привычные инструменты 1С 7.7.