Как ускорить обход результата запроса через ADODB.Connection в 1С?

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

При интеграции 1С со сторонними базами данных через COM-объект ADODB.Connection разработчики нередко сталкиваются с ситуацией, когда сам SQL-запрос на сервере СУБД выполняется за доли секунды, но последующий перебор строк в цикле 1С занимает колоссальное количество времени. Замер производительности показывает, что основное время (до 80-90%) тратится непосредственно на вызов метода RecordSet.MoveNext().

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

Почему MoveNext() работает медленно: анализ проблемы

Проанализируем типовой фрагмент кода, который чаще всего приводит к просадке производительности:


Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Open(СтрокаПодключения);
RecordSet = Соединение.Execute(ТекстЗапросаADO);

Пока RecordSet.EOF() = 0 Цикл
    ЗначениеКолонки = RecordSet.Fields("ИмяПоля").Value;
    RecordSet.MoveNext();
КонецЦикла;

Выясним причину задержек при таком подходе:

  1. Серверный курсор по умолчанию и отсутствие буферизации: Метод Connection.Execute() возвращает Recordset с серверным курсором (CursorLocation = 2 / adUseServer) и размером кэша по умолчанию CacheSize = 1. Это означает, что при каждом вызове MoveNext() драйвер обращается по сети к СУБД за получением ровно одной следующей строки.
  2. Накладные расходы COM-маршалинга: Каждый шаг цикла и каждое обращение к RecordSet.Fields("ИмяПоля").Value совершают межпроцессные вызовы через позднее связывание (IDispatch). На выборках в десятки тысяч строк такие микрозадержки суммируются в минуты ожидания.
  3. Передача тяжелых данных: Если запрос выбирает BLOB-поля (двоичные данные, длинные тексты), СУБД может подгружать их содержимое порциями именно в момент обхода курсора.

Способ 1. Настройка параметров Recordset и свойства CacheSize

Чтобы заставить 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() начинает отрабатывать значительно быстрее.

Способ 2. Получение данных одной строкой через GetString()

Если требуется максимально сократить число 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] и т.д.
    КонецЦикла;
КонецЕсли;

Способ 3. Оптимизация прямого цикла и кэширование полей Fields

Если логика алгоритма требует построчного обхода объекта 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();
КонецЦикла;

Способ 4. Оптимизация на стороне СУБД

Проанализируем текст самого SQL-запроса и настройки сессии:

  1. Директива SET NOCOUNT ON: В начале SQL-скрипта всегда указывайте команду SET NOCOUNT ON;. Без нее MS SQL Server отправляет клиенту служебные сообщения о количестве обработанных строк после каждого оператора, что приводит к созданию лишних внутренних пакетов в ADO.
  2. Формирование JSON/XML на стороне SQL: Если версия MS SQL Server поддерживает конструкцию FOR JSON PATH или FOR XML PATH, целесообразно переложить сериализацию на СУБД. Запрос вернет единственную строку с готовым JSON-документом, который считывается объектом ЧтениеJSON за один проход.

Альтернативные архитектурные решения

Если объем передаваемых данных составляет сотни тысяч или миллионы записей, механизм COM/ADO становится узким местом из-за 32/64-битного моста и накладных расходов платформы. В таких сценариях рекомендуется рассмотреть альтернативы:

  1. Выгрузка через утилиты СУБД в файл: Использование утилиты bcp или скриптов экспорта в CSV-файл на стороне сервера с последующим чтением в 1С через объект ЧтениеТекста или ЧтениеCSV.
  2. Использование внешней компоненты (Native API): Написание или подключение готовой Native-компоненты на C++/C#, выполняющей прямой запрос через ODBC-драйвер и возвращающей данные непосредственно в структуры или таблицы 1С в обход COM.
← На главную