При интеграции 1С со сторонними базами данных через COM-объект ADODB.Connection разработчики нередко сталкиваются с ситуацией, когда сам SQL-запрос на сервере СУБД выполняется за доли секунды, но последующий перебор строк в цикле 1С занимает колоссальное количество времени. Замер производительности показывает, что основное время (до 80-90%) тратится непосредственно на вызов метода RecordSet.MoveNext().
Разберем причины такого поведения и рассмотрим комплекс практических решений для многократного ускорения выборки данных.
Проанализируем типовой фрагмент кода, который чаще всего приводит к просадке производительности:
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Open(СтрокаПодключения);
RecordSet = Соединение.Execute(ТекстЗапросаADO);
Пока RecordSet.EOF() = 0 Цикл
ЗначениеКолонки = RecordSet.Fields("ИмяПоля").Value;
RecordSet.MoveNext();
КонецЦикла;
Выясним причину задержек при таком подходе:
Connection.Execute() возвращает Recordset с серверным курсором (CursorLocation = 2 / adUseServer) и размером кэша по умолчанию CacheSize = 1. Это означает, что при каждом вызове MoveNext() драйвер обращается по сети к СУБД за получением ровно одной следующей строки.RecordSet.Fields("ИмяПоля").Value совершают межпроцессные вызовы через позднее связывание (IDispatch). На выборках в десятки тысяч строк такие микрозадержки суммируются в минуты ожидания.Чтобы заставить ADO запрашивать данные с сервера пачками, а не по одной строке, откажемся от вызова Соединение.Execute() и выполним явную инициализацию объекта ADODB.Recordset с настройкой параметров.
Посмотрим на пример правильной настройки набора записей:
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Open(СтрокаПодключения);
RecordSet = Новый COMОбъект("ADODB.Recordset");
// 3 = adUseClient (клиентский курсор) или 2 = adUseServer (серверный)
RecordSet.CursorLocation = 3;
// Увеличиваем размер кэша строк (по умолчанию 1)
RecordSet.CacheSize = 1000;
// Открываем набор записей:
// CursorType: 0 = adOpenForwardOnly (только вперед, самый быстрый)
// LockType: 1 = adLockReadOnly (только для чтения)
RecordSet.Open(ТекстЗапросаADO, Соединение, 0, 1);
Пока RecordSet.EOF() = 0 Цикл
// Обработка данных
RecordSet.MoveNext();
КонецЦикла;
RecordSet.Close();
Соединение.Close();
Увеличение CacheSize до 500–5000 строк резко сокращает количество сетевых пакетов между клиентом 1С и сервером СУБД, за счет чего MoveNext() начинает отрабатывать значительно быстрее.
Если требуется максимально сократить число COM-вызовов, воспользуемся методом GetString(). Он возвращает весь результат запроса целиком в виде одной текстовой строки с заданными разделителями колонок и строк (аналог CSV/TSV). Обработка текста штатными средствами 1С происходит в десятки раз быстрее, чем перебор COM-объектов.
Разберем реализацию этого способа:
RecordSet = Новый COMОбъект("ADODB.Recordset");
RecordSet.CursorLocation = 3;
RecordSet.Open(ТекстЗапросаADO, Соединение, 0, 1);
Если Не RecordSet.EOF() Тогда
// Параметры GetString:
// 1 - StringFormat (2 = adClipString)
// 2 - NumRows (-1 = все строки)
// 3 - ColumnDelimiter (разделитель колонок, например, Таб)
// 4 - RowDelimiter (разделитель строк, например, ПС)
// 5 - NullExpr (строка для замены NULL)
СимволТаб = Символы.Таб;
СимволПС = Символы.ПС;
РезультатСтрокой = RecordSet.GetString(2, -1, СимволТаб, СимволПС, "");
RecordSet.Close();
// Быстрый разбор строк в 1С
МассивСтрок = СтрРазделить(РезультатСтрокой, СимволПС, Ложь);
Для Каждого СтрДанных Из МассивСтрок Цикл
Колонки = СтрРазделить(СтрДанных, СимволТаб, Истина);
// Колонки[0], Колонки[1] и т.д.
КонецЦикла;
КонецЕсли;
Если логика алгоритма требует построчного обхода объекта Recordset, исключим избыточные COM-вызовы внутри цикла. Обращение по имени поля вида RecordSet.Fields("Колонка") выполняет поиск строки в коллекции на каждой итерации. Заменим его предварительным получением прямых ссылок на поля по числовым индексам.
Рассмотрим пример оптимизированного цикла:
RecordSet = Новый COMОбъект("ADODB.Recordset");
RecordSet.CacheSize = 1000;
RecordSet.Open(ТекстЗапросаADO, Соединение, 0, 1);
// Кэшируем ссылки на объекты полей до входа в цикл
Поле1 = RecordSet.Fields.Item(0);
Поле2 = RecordSet.Fields.Item(1);
Пока RecordSet.EOF() = 0 Цикл
// Обращение напрямую к закэшированным объектам COM
Значение1 = Поле1.Value;
Значение2 = Поле2.Value;
RecordSet.MoveNext();
КонецЦикла;
Проанализируем текст самого SQL-запроса и настройки сессии:
SET NOCOUNT ON;. Без нее MS SQL Server отправляет клиенту служебные сообщения о количестве обработанных строк после каждого оператора, что приводит к созданию лишних внутренних пакетов в ADO.FOR JSON PATH или FOR XML PATH, целесообразно переложить сериализацию на СУБД. Запрос вернет единственную строку с готовым JSON-документом, который считывается объектом ЧтениеJSON за один проход.Если объем передаваемых данных составляет сотни тысяч или миллионы записей, механизм COM/ADO становится узким местом из-за 32/64-битного моста и накладных расходов платформы. В таких сценариях рекомендуется рассмотреть альтернативы:
bcp или скриптов экспорта в CSV-файл на стороне сервера с последующим чтением в 1С через объект ЧтениеТекста или ЧтениеCSV.