Как организовать постраничную выборку данных из Oracle в 1С через ADO и избежать зависания?

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

При интеграции 1С с СУБД Oracle через COM-объекты ADO разработчики нередко сталкиваются с ситуацией: в специализированных средах (например, Oracle SQL Developer или PL/SQL Developer) запрос к многомиллионной таблице отрабатывает мгновенно, а при вызове аналогичного запроса из 1С процесс намертво зависает или аварийно завершается с ошибкой переполнения памяти. Разберем причины такого поведения и рассмотрим надежные способы постраничной выборки данных (пейджинга).

Почему в SQL Developer запрос работает быстро, а в 1С зависает?

Проанализируем разницу в поведении сред разработки и платформы 1С:

  1. Серверные курсоры и постраничная подгрузка в GUI. Среды разработки по умолчанию не выгружают весь массив строк. Они открывают курсор на сервере СУБД и отображают первые 50–100 записей, подтягивая следующие порции данных по мере прокрутки таблицы пользователем.
  2. Загрузка всей выборки в память 1С. При стандартном обращении через ADODB.Recordset или метод Connection.Execute() по умолчанию весь результирующий набор данных пытается загрузиться в оперативную память сеанса 1С.
  3. Накладные расходы COM-вызовов. Построчный обход выборки в цикле 1С с помощью конструкции MoveNext() порождает миллионы контекстных переключений между средой 1С и объектом COM, что приводит к многократному падению производительности.

Способ 1. Ограничение выборки на уровне SQL-запроса в Oracle

Наиболее эффективный подход — переложить расчет порций на сервер СУБД и запрашивать из 1С только нужный диапазон строк (с N1 по N2).

Вариант для Oracle 12c и более новых версий:

В современных версиях Oracle реализован стандартный синтаксис постраничной навигации через OFFSET и FETCH. Обязательным условием является наличие блока ORDER BY для детерминированного порядка строк.


SELECT * FROM MyTable
ORDER BY Id
OFFSET 50 ROWS FETCH NEXT 50 ROWS ONLY;

Вариант для Oracle 11g и более ранних версий (через ROWNUM):

В устаревших версиях Oracle прямое использование OFFSET не поддерживается. Для выборки диапазона строк применяется трехуровневая обертка с псевдоколонкой ROWNUM. Обратите внимание: фильтрацию по ROWNUM нельзя делать в одном подзапросе с ORDER BY, так как псевдоколонка присваивается строкам до выполнения сортировки.


SELECT * FROM (
    SELECT a.*, ROWNUM rnum FROM (
        SELECT * FROM MyTable ORDER BY Id
    ) a
    WHERE ROWNUM <= 100
)
WHERE rnum >= 51;

Вариант с аналитической функцией ROW_NUMBER():

Альтернативный универсальный способ нумерации строк с предварительной сортировкой:


SELECT * FROM (
    SELECT t.*, ROW_NUMBER() OVER (ORDER BY Id) AS rn
    FROM MyTable t
)
WHERE rn BETWEEN 51 AND 100;

Способ 2. Настройка параметров курсора и пакетная выгрузка в ADO

Если текст исходного запроса менять нежелательно, настроим параметры COM-объекта ADODB.Recordset непосредственно в коде 1С, чтобы сервер не передавал все данные разом.

  1. Установим серверный курсор: свойство CursorLocation = 2 (константа adUseServer).
  2. Установим размер локального буфера: свойство CacheSize = 50 (определяет количество строк, единовременно передаваемых по сети).
  3. Используем метод GetRows() вместо медленного построчного цикла MoveNext(). Метод возвращает двумерный массив, который платформа 1С считывает мгновенно.

Рассмотрим пример кода на стороне 1С:


// Подключение к Oracle через ADO
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=MyOracleDB;User Id=my_user;Password=my_password;";
Соединение.Open();

// Настройка набора записей
НаборЗаписей = Новый COMОбъект("ADODB.Recordset");
НаборЗаписей.CursorLocation = 2; // adUseServer
НаборЗаписей.CacheSize = 50;

ТекстЗапроса = "SELECT Id, Name, Amount FROM MyTable ORDER BY Id";
НаборЗаписей.Open(ТекстЗапроса, Соединение, 0, 1); // adOpenForwardOnly, adLockReadOnly

// Чтение порциями по 50 строк
РазмерПачки = 50;
Пока НЕ НаборЗаписей.EOF Цикл
    // GetRows возвращает массив данных текущей пачки
    МассивДанных = НаборЗаписей.GetRows(РазмерПачки);
    
    // Обработка полученных строк пачки
    // В COMSafeArray первое измерение - колонки, второе - строки
    КоличествоКолонок = МассивДанных.UBound(1);
    КоличествоСтрок = МассивДанных.UBound(2);
    
    Для ИндексСтроки = 0 По КоличествоСтрок Цикл
        Идентификатор = МассивДанных.GetValue(0, ИндексСтроки);
        Наименование = МассивДанных.GetValue(1, ИндексСтроки);
        Сумма = МассивДанных.GetValue(2, ИндексСтроки);
        
        // Логика обработки данных в 1С
    КонецЦикла;
КонецЦикла;

НаборЗаписей.Close();
Соединение.Close();

Способ 3. Использование механизма «Внешние источники данных»

Если требуется регулярная интеграция с таблицами Oracle без использования низкоуровневых COM-объектов, оптимально использовать штатный механизм платформы 1С — Внешние источники данных (ВИД).

  1. В дереве метаданных конфигуратора добавляется новый внешний источник данных и настраивается подключение через установленный в операционной системе ODBC-драйвер Oracle.
  2. Таблицы Oracle добавляются в состав внешнего источника данных как объекты метаданных 1С.
  3. Работа с данными ведется стандартным языком запросов 1С (с поддержкой конструкции ВЫБРАТЬ ПЕРВЫЕ...), а при выводе в динамические списки платформа 1С автоматически берет на себя постраничную порционную подгрузку данных без риска зависания сеанса.

Важное замечание о разрядности: при выполнении интеграции на стороне сервера 1С:Предприятия разрядность установленного драйвера Oracle (ODAC / Instant Client) должна строго совпадать с разрядностью службы ragent (для 64-разрядного сервера 1С требуется 64-битный клиент Oracle).

← На главную