Как устранить ошибку Connection is busy with results for another hstmt в 1С++ при обходе выборки SQL

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При разработке прямых запросов к базе данных SQL в 1С:Предприятии 7.7 с использованием внешней компоненты 1С++ разработчики часто сталкиваются с ошибкой ODBC Connection is busy with results for another hstmt. Она возникает в процессе построчного обхода выборки через объект ODBCRecordSet при попытке прочитать типизированные поля или получить ссылки на объекты 1С.

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

Почему возникает ошибка Connection is busy with results for another hstmt

Проанализируем механизм работы компоненты. По умолчанию объект ODBCRecordSet использует текущее активное подключение платформы к Microsoft SQL Server. При открытии выборки методом Открыть() создается последовательный серверный курсор (SQL_CURSOR_FORWARD_ONLY).

В такой конфигурации дескриптор соединения с базой (HDBC) остается полностью занят активной выборкой (HSTMT) до тех пор, пока все записи не будут прочитаны или пока не будет вызван метод rs.Закрыть().

Если внутри цикла обхода выборки мы пытаемся:

  1. Использовать автоматическую типизацию ссылочных полей через конструкции метапарсера вроде [$Справочник.МестаХранения];
  2. Вызывать методы преобразования ссылок, такие как MDW.ЗначениеИзСтрокиБД();
  3. Обращаться к реквизитам объектов 1С стандартными средствами встроенного языка.

Платформа пытается отправить дополнительный SQL-запрос к серверу через то же самое соединение, которое в этот момент уже занято обработкой открытого курсора. ODBC-драйвер блокирует эту операцию и возвращает ошибку Connection is busy with results for another hstmt.

Способ 1. Изменение типа курсора на статический

Чтобы освободить поток подключения сразу после открытия набора данных, настроим свойства курсора перед вызовом метода Открыть(). Для этого используем метод УстСвойстваКурсора() с параметром статического курсора (SQL_CURSOR_STATIC = 2).

Посмотрим на пример правильной настройки:


rs = СоздатьОбъект("ODBCRecordSet");
// Устанавливаем статический тип курсора (2 - SQL_CURSOR_STATIC)
rs.УстСвойстваКурсора(2, 0, 0, 0);

Если rs.Открыть(ТекстЗапроса) = 1 Тогда
    // Задаем типы колонок строго после успешного открытия курсора
    rs.УстТипыКолонок1С("Справочник.МПЗ,Справочник.Контрагенты,Справочник.МестаХранения,Число,Число");
    
    rs.ВНачало();
    Пока rs.Конец() = 0 Цикл
        Сообщить("МПЗ: " + rs.ПолучитьЗначение(1));
        Сообщить("Остаток: " + rs.ПолучитьЗначение(4));
        rs.След();
    КонецЦикла;
    
    rs.Закрыть();
Иначе
    Сообщить("Ошибка открытия выборки: " + rs.ПолучитьОписаниеОшибки());
КонецЕсли;

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

Способ 2. Выгрузка результата в ТаблицуЗначений

Если объем данных умещается в оперативной памяти, наиболее надежным и производительным решением является полный отказ от построчного серверного курсора в пользу пакетной выгрузки в ТаблицуЗначений.

Рассмотрим, как выполнить запрос с типизацией через метод ВыполнитьИнструкцию():


rs = СоздатьОбъект("ODBCRecordSet");
ТекстЗапроса = "
|SELECT
|   Спр.ID as [МПЗ $Справочник.МПЗ],
|   Спр.DESCR as Наименование,
|   Рег.ОстатокТовара as [Остаток #Число.15.2]
|FROM
|   $Справочник.МПЗ as Спр
|";

ТЗ = rs.ВыполнитьИнструкцию(ТекстЗапроса);
Если ПустоеЗначение(ТЗ) = 0 Тогда
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        Сообщить(Строка(ТЗ.МПЗ) + " - " + ТЗ.Остаток);
    КонецЦикла;
КонецЕсли;

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

Способ 3. Использование отдельного независимого подключения к SQL Server

Если требуется непрерывный потоковый обход выборки очень большого объема без загрузки всех строк в память, создадим отдельную физическую сессию к базе данных с помощью объекта ODBCDataBase.

Разберем по шагам создание отдельного подключения:

  1. Создаем объект базы данных ODBCDataBase и подключаемся через строку соединения или DSN (не используем метод Attach1C(), так как он задействует общий дескриптор платформы);
  2. Связываем объект выборки с созданной базой через rs.УстБД(БазаДанных);
  3. Выполняем запрос в изолированном соединении, не мешая штатным механизмам 1С.

БД = СоздатьОбъект("ODBCDataBase");
Если БД.Соединить("DRIVER={SQL Server};SERVER=MyServer;UID=sa;PWD=secret;DATABASE=MyBase1C;") = 1 Тогда
    rs = СоздатьОбъект("ODBCRecordSet");
    rs.УстБД(БД);
    
    Если rs.Открыть(ТекстЗапроса) = 1 Тогда
        Пока rs.След() = 1 Цикл
            // Теперь запросы платформы и чтение выборки идут по разным соединениям
            Ид = rs.ПолучитьЗначение(1);
            Объект = MDW.ЗначениеИзСтрокиБД(11, "Контрагенты", Ид);
            Сообщить("Элемент: " + Объект);
        КонецЦикла;
        rs.Закрыть();
    КонецЕсли;
    БД.Отсоединить();
КонецЕсли;

Правила вызова метода УстТипыКолонок1С

Обратим внимание на ошибку "Данный метод возможно вызывать только после подготовки запроса или открытия курсора", которая часто возникает при попытке типизации полей. Чтобы избежать этой ошибки, соблюдаем следующие требования:

  1. Вызываем УстТипыКолонок1С() строго после успешного выполнения метода rs.Открыть() (когда rs.ВыборкаОткрыта() = 1) или после вызова rs.Подготовить();
  2. Количество типов данных в строке параметров должно точно совпадать с числом колонок в блоке SELECT запроса;
  3. Порядок типов должен строго соответствовать очередности полей в выборке.
← На главную