Как ускорить выгрузку данных из объекта ADO RecordSet в ТаблицуЗначений?

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

Работа с внешними базами данных через ADO (ActiveX Data Objects) — распространенная задача при интеграции 1С с другими системами (SQL Server, Access, Oracle, Informix и др.). Однако часто разработчики сталкиваются с проблемой производительности: получение данных происходит быстро, а вот перенос этих данных из COM-объекта RecordSet в родную для 1С ТаблицаЗначений занимает непозволительно много времени.

В этой статье мы подробно разберем, почему возникает эта проблема, и рассмотрим несколько эффективных способов ускорения выгрузки данных, основанных на практике сообщества разработчиков. Мы пройдем путь от стандартного (медленного) цикла до использования массовой выгрузки и тонкой настройки параметров соединения.

Почему стандартный метод работает медленно?

Рассмотрим классический пример кода, который чаще всего пишут начинающие разработчики. Он заключается в переборе записей набора данных в цикле While и переборе полей внутри каждой строки.


// Медленный способ
COMRecordSet.Open(ТекстЗапросаSQL); 
COMRecordSet.MoveFirst(); 

Пока COMRecordSet.EOF() = 0 Цикл 
    НоваяСтрока = ТаблицаЗначений.Добавить(); 
    Для Каждого Поле Из COMRecordSet.Fields() Цикл 
        НоваяСтрока[Поле.Name] = COMRecordSet.Fields(Поле.Name).Value; 
    КонецЦикла; 
    COMRecordSet.MoveNext();
КонецЦикла;

В чем причина "тормозов"?

Основная проблема кроется в маршалинге (передаче данных) между средой исполнения 1С и COM-объектом. В приведенном выше коде при каждом обращении к COMRecordSet.Fields, Value или EOF происходит вызов внешнего объекта. Если у вас 5000 строк и 20 колонок, это означает 100 000+ переключений контекста и обращений к COM-интерфейсу. Это создает огромные накладные расходы.

Способ 1. Использование метода GetRows (Основное решение)

Самый эффективный способ ускорить процесс — минимизировать количество обращений к COM-объекту. Вместо того чтобы тянуть данные по одной ячейке, мы можем забрать весь набор данных (или его часть) одним вызовом. Для этого в ADO существует метод GetRows.

Метод GetRows возвращает двумерный массив (COMSafeArray), содержащий данные запроса. В 1С мы можем преобразовать его в обычный массив и работать с ним уже внутри памяти 1С, что на порядки быстрее.

Важный нюанс: Массив, возвращаемый методом GetRows, является транспонированным. Это означает, что первым индексом в нем идет номер колонки, а вторым — номер строки. То есть формат данных: Массив[Колонка][Строка].

Разберем оптимизированный алгоритм:

  1. Выполняем запрос и получаем RecordSet.
  2. Формируем структуру ТаблицаЗначений (создаем колонки).
  3. Выгружаем данные в массив через GetRows.
  4. Обходим массив 1С и заполняем таблицу.

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


// Создаем соединение и выполняем запрос
RecordSet = Connection.Execute(ТекстЗапроса);

Если RecordSet.EOF = 0 Тогда
    
    // 1. Подготовка ТаблицыЗначений
    ТЗ = Новый ТаблицаЗначений;
    КоличествоКолонок = RecordSet.Fields.Count - 1;
    
    // Создаем колонки в ТЗ по именам полей из запроса
    Для Сч = 0 По КоличествоКолонок Цикл
        ИмяКолонки = RecordSet.Fields(Сч).Name;
        ТЗ.Колонки.Добавить(ИмяКолонки);
    КонецЦикла;
    
    // 2. Массовое получение данных
    // COMSafeArray преобразуем в массив 1С методом Выгрузить() (или Unload в англ. синтаксисе)
    МассивДанных = RecordSet.GetRows().Выгрузить();
    
    // Получаем границы массива
    // Напомним: МассивДанных[Колонка][Строка]
    КоличествоСтрок = МассивДанных[0].ВГраница(); 
    
    // 3. Заполнение ТаблицыЗначений
    // Предварительно создаем нужное количество строк для скорости
    Для ИндСтр = 0 По КоличествоСтрок Цикл
        НоваяСтрока = ТЗ.Добавить();
        
        // Заполняем поля, обращаясь к массиву
        Для ИндКол = 0 По КоличествоКолонок Цикл
             // Обратите внимание на порядок индексов: [Колонка][Строка]
             НоваяСтрока[ИндКол] = МассивДанных[ИндКол][ИндСтр];
        КонецЦикла;
    КонецЦикла;
    
КонецЕсли;

RecordSet.Close();

Практика показывает, что для набора в 5000 строк и 20 полей время выгрузки снижается с 9–10 секунд до долей секунды. Это колоссальный прирост производительности.

Способ 2. Настройка параметра CacheSize

Даже при использовании GetRows, выборка данных может работать недостаточно быстро, если ADO запрашивает у сервера данные слишком мелкими порциями. На помощь приходит свойство CacheSize объекта RecordSet.

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

Попробуйте установить размер кэша перед открытием набора данных или перед перебором:


RecordSet = Новый COMОбъект("ADODB.RecordSet");
RecordSet.CursorLocation = 3; // adUseClient - важно для клиентского кэширования
RecordSet.CacheSize = 500;    // Размер пачки записей
RecordSet.Open(ТекстЗапроса, Connection);

Увеличение CacheSize до 100, 500 или даже 1000 может значительно сократить количество сетевых обращений к серверу БД. В одном из примеров время обработки 150 000 строк сократилось с 60 секунд до 1 секунды после настройки этого параметра.

Способ 3. Выбор правильного провайдера (Provider)

Иногда проблема кроется не в коде 1С, а в драйвере, который используется для подключения. Разные провайдеры имеют разную производительность при работе с курсорами.

Если вы используете устаревшие драйверы ODBC (например, Microsoft Access Driver), стоит попробовать переключиться на нативные OLEDB провайдеры (например, Microsoft.Jet.OLEDB.4.0 для MDB файлов или SQLNCLI / MSOLEDBSQL для MS SQL Server).

Пример изменения строки подключения, который дал прирост производительности в 10 раз:

Проверьте вашу строку подключения (Connection String) и убедитесь, что вы используете наиболее актуальный и прямой метод доступа к данным.

Способ 4. Типизация ТаблицыЗначений

Еще один момент, который часто упускают — типизация колонок при создании ТаблицаЗначений. Если вы создаете колонку просто методом ТЗ.Колонки.Добавить("Имя"), то тип значения в колонке не определен (составной). При записи данных платформа 1С тратит время на определение типа и выделение памяти.

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


// Пример создания типизированной колонки (строка 100 символов)
ТЗ.Колонки.Добавить("CustomerName", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100)));

Дополнительные рекомендации

При работе с выгрузкой больших объемов данных через ADO стоит учитывать еще несколько нюансов:

  1. Значения NULL. SQL-серверы могут возвращать NULL. В COM-объекте это специальное значение. При попытке записать его напрямую в типизированную колонку 1С (например, числовую) возникнет ошибка. Рекомендуется либо обрабатывать NULL на стороне SQL-запроса (функция ISNULL или COALESCE), заменяя их на пустые строки или нули, либо проверять каждое значение в цикле 1С.
  2. Тип Курсора. Для максимальной скорости чтения используйте "Клиентский курсор" (CursorLocation = 3, adUseClient). Это позволяет загрузить данные в память машины, где выполняется код 1С, и работать с ними локально, что значительно ускоряет методы GetRows и RecordCount.
  3. Временные таблицы. Если ваш SQL-запрос очень сложный и включает вычисляемые поля (особенно через XML/XQuery), само выполнение запроса может тормозить процесс получения данных через ADO. В таких случаях помогает сохранение результатов выборки во временную таблицу на SQL-сервере, а затем простое чтение (SELECT * FROM #TempTable) уже средствами ADO.

Резюме

Чтобы максимально ускорить загрузку данных из ADO в 1С:

  1. Используйте метод GetRows() для получения данных массивом.
  2. Не забывайте, что массив транспонирован: индексы идут как [Колонка][Строка].
  3. Устанавливайте параметр CacheSize (например, 500-1000).
  4. Используйте CursorLocation = 3 (adUseClient).
  5. Выбирайте наиболее быстрый OLEDB провайдер для вашей СУБД.
← На главную