При работе с большими объемами данных в устаревшей, но все еще актуальной платформе 1С:Предприятие 7.7 программисты часто сталкиваются с аппаратными и программными ограничениями. Одной из самых острых проблем является обработка выборок, содержащих миллион и более строк. Стандартная «Таблица Значений» (ТЗ) в 1С 7.7 начинает «валить» программу уже на 400 000 строк из-за нехватки оперативной памяти, так как 32-битное приложение не может адресовать более 2-3 ГБ ОЗУ. В этой статье мы разберем, как правильно организовать порционное получение данных из SQL, используя возможности библиотеки 1С++.
Прежде чем перейти к решениям, разберем типичную техническую проблему. При попытке обойти результат запроса через Recordset и одновременном получении ссылок на объекты 1С (типизации), часто возникает ошибка: "1C++: Connection is busy with results for another hstmt".
Проанализируем ситуацию: это происходит потому, что 1С пытается выполнить дополнительные подзапросы к базе данных (например, для получения представления ссылки или обращения к реквизиту через точку), пока основное соединение все еще занято открытым курсором SQL-запроса. Для решения этой проблемы мы можем пойти двумя путями: либо выгружать данные во временную структуру, либо использовать отдельное соединение для основного запроса.
Наиболее надежный способ обработки миллионных выборок — это перенос результата основного запроса во временную таблицу на стороне SQL-сервера с добавлением уникального индекса (identity). Это позволяет нам «нарезать» результат на любые удобные порции (например, по 50 000 строк) и обрабатывать их последовательно.
Рассмотрим алгоритм реализации по шагам:
ROW_ID INT IDENTITY.INSERT INTO ... SELECT).WHERE ROW_ID BETWEEN ... AND ... или SELECT TOP 50000 ... WHERE ROW_ID > @LastID.Пример логики запроса для получения порции данных:
// Пример SQL-запроса для получения следующей порции
ТекстЗапроса = "
|SELECT TOP 50000 *
|FROM #TempResults
|WHERE ROW_ID > " + ПоследнийИД + "
|ORDER BY ROW_ID";
Такой подход гарантирует, что в оперативной памяти 1С одновременно находится только небольшой объем данных, что предотвращает аварийное завершение программы.
Если выгрузка во временную таблицу SQL по каким-то причинам неудобна, можно использовать объект ИндексированнаяТаблица из состава 1С++. В отличие от стандартной ТЗ, ИТЗ лучше оптимизирована по памяти и предоставляет более гибкие механизмы работы.
Посмотрим на пример выгрузки результата запроса напрямую в ИндексированнаяТаблица:
ИТЗ = СоздатьОбъект("ИндексированнаяТаблица");
// Метод ВыполнитьИнструкцию может принимать ИТЗ в качестве параметра для выгрузки
ОДБК = СоздатьОбъект("ODBCRecordset");
ОДБК.ВыполнитьИнструкцию(ТекстЗапроса, ИТЗ);
Важный момент: хотя ИТЗ работает быстрее, она все равно потребляет память процесса. Для миллиона строк со ссылками даже ИТЗ может не хватить. Если вы получили ошибку "Error: Cannot allocate memory", значит, объем данных превысил возможности 32-битной архитектуры, и вам необходимо вернуться к порционному чтению (Решение 1).
Если миллион строк нужно не просто обработать программно, а показать пользователю, использование обычных методов вывода приведет к зависанию интерфейса. В 1С++ для этих целей предусмотрен объект ТабличноеПоле, который работает по принципу виртуального списка (подгружает только те данные, которые видны на экране).
Разберем, как связать результат запроса с экранной формой через ПоставщикДанныхТЗ:
// Процедура инициализации на форме
_Форма = СоздатьОбъект("Форма");
_Форма.УстановитьФорму(Форма);
// Создаем элемент управления на форме (нужно заранее разместить кнопку или текст-пустышку)
ТП = _Форма.СоздатьЭлементУправления("ТабличноеПоле", "Результаты");
ТП.ПоставщикДанных = СоздатьОбъект("ПоставщикДанныхТЗ");
// Создаем ИТЗ и наполняем её данными
ТЗ_Данные = СоздатьОбъект("ИндексированнаяТаблица");
// ... код заполнения ТЗ_Данные из SQL ...
// Привязываем таблицу к поставщику данных
ТП.ПоставщикДанных.ТаблицаЗначений = ТЗ_Данные;
Это позволяет пользователю плавно прокручивать даже очень большие списки без существенной нагрузки на систему.
Проанализируем еще несколько приемов, которые помогут при работе с критическими объемами данных:
ID объектов в ссылки прямо в SQL-запросе, если планируете только перекладывать данные. Преобразуйте их в объекты 1С непосредственно перед выполнением действия (например, перед созданием документа).ROW_NUMBER() OVER (ORDER BY ...) для организации пагинации на стороне сервера без создания физических временных таблиц.Connection is busy, создайте для выполнения запроса отдельный объект ODBCConnection и передайте его в ODBCRecordset методом УстановитьСвязь(). Это позволит 1С выполнять служебные запросы через основное соединение, не конфликтуя с вашим курсором.[dt] и [dh] таблицы), минуя объектную модель 1С. Это на порядок быстрее и не требует памяти для объектов 1С, но требует глубокого понимания структуры таблиц и генерации уникальных ID.Таким образом, комбинируя порционное чтение через временные таблицы и использование оптимизированных объектов библиотеки 1С++, можно эффективно решать задачи по обработке данных любого объема в среде 1С 7.7.