При интеграции 1С с СУБД Oracle через COM-объекты ADO разработчики нередко сталкиваются с ситуацией: в специализированных средах (например, Oracle SQL Developer или PL/SQL Developer) запрос к многомиллионной таблице отрабатывает мгновенно, а при вызове аналогичного запроса из 1С процесс намертво зависает или аварийно завершается с ошибкой переполнения памяти. Разберем причины такого поведения и рассмотрим надежные способы постраничной выборки данных (пейджинга).
Проанализируем разницу в поведении сред разработки и платформы 1С:
ADODB.Recordset или метод Connection.Execute() по умолчанию весь результирующий набор данных пытается загрузиться в оперативную память сеанса 1С.MoveNext() порождает миллионы контекстных переключений между средой 1С и объектом COM, что приводит к многократному падению производительности.Наиболее эффективный подход — переложить расчет порций на сервер СУБД и запрашивать из 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;
Если текст исходного запроса менять нежелательно, настроим параметры COM-объекта ADODB.Recordset непосредственно в коде 1С, чтобы сервер не передавал все данные разом.
CursorLocation = 2 (константа adUseServer).CacheSize = 50 (определяет количество строк, единовременно передаваемых по сети).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();
Если требуется регулярная интеграция с таблицами Oracle без использования низкоуровневых COM-объектов, оптимально использовать штатный механизм платформы 1С — Внешние источники данных (ВИД).
ВЫБРАТЬ ПЕРВЫЕ...), а при выводе в динамические списки платформа 1С автоматически берет на себя постраничную порционную подгрузку данных без риска зависания сеанса.Важное замечание о разрядности: при выполнении интеграции на стороне сервера 1С:Предприятия разрядность установленного драйвера Oracle (ODAC / Instant Client) должна строго совпадать с разрядностью службы ragent (для 64-разрядного сервера 1С требуется 64-битный клиент Oracle).