Как исправить ошибку выполнения прямого SQL-запроса через дополнительное ODBC-соединение в 1С 7.7?

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

При использовании внешней компоненты 1С++ для выполнения прямых SQL-запросов к MS SQL Server разработчики периодически сталкиваются с ситуацией, когда запрос через дополнительное соединение зацикливается, возвращает пустую выборку или вызывает ошибку ODBC: State HY000, native 0, message [Microsoft][ODBC SQL Server Driver]Подключение занято до получения результатов для другого hstmt.

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

Анализ проблемы и исходного кода

Рассмотрим исходный фрагмент кода, в котором возникает ошибка зависания при обходе выборки:


МД = СоздатьОбъект("MetaDataWork");
бд = СоздатьОбъект("ODBCDatabase");
рс = СоздатьОбъект("ODBCRecordset");

сервер = "server";
БазаДанных = "ggg";
Пользователь = "d";
Пароль = "d"; 

СтрокаПодключения = "Provider=SQLOLEDB.1;User ID=" + Пользователь + ";Pwd=" + Пароль + ";Initial Catalog=" + БазаДанных + ";Data Source=" + сервер + ";";
бд.Соединение(СтрокаПодключения);
рс.Отладка(1);
рс.УстБД(бд);

ТекстЗапроса = "
|SELECT TOP 2 
|    ТКОстатки.Комитент Комитент
|    , ТКОстатки.ЮридическоеЛицо ЮридическоеЛицо
|    , ТКОстатки.МестоХранения МестоХранения
|    , ТКОстатки.МПЗМ МПЗ
|    , ТКОстатки.КоличествоОстаток
|    , МестаХранения.ID МХМПЗ
|    , Контрагенты.ID ЮЛМПЗ
|FROM $РегистрОстатки.ТК(,,
|        (ВидОстатка = $Перечисление.ВидыОстатков.ОтклоненияБухУчета)
|        AND (Комитент = :Комитент),(Комитент,ЮридическоеЛицо,МестоХранения,МПЗ),(Количество)) AS ТКОстатки
|    JOIN $Справочник.МПЗ AS МПЗ With (NOLOCK) ON ТКОстатки.МПЗ = МПЗ.ID
|    JOIN $Справочник.МестаХранения AS МестаХранения With (NOLOCK) ON $МПЗ.МестоХранения = МестаХранения.ID
|    JOIN $Справочник.Контрагенты AS Контрагенты With (NOLOCK) ON $ПоследнееЗначение.МестаХранения.ЮридическоеЛицо(МестаХранения.ID, :ВыбДата) = Контрагенты.ID
|Where ТКОстатки.МестоХранения <> МестаХранения.ID OR ТКОстатки.ЮридическоеЛицо <> Контрагенты.ID 
|";

МД.УстановитьТекстовыйПараметр("ВыбДата", ДатаОСт);
МД.УстановитьТекстовыйПараметр("Комитент", Комитент);

ТекстЗапроса = МД.ОбрМетаСКЛ(ТекстЗапроса);

рс.Открыть(ТекстЗапроса);

рс.ВНачало();
Пока рс.Конец() = 0 Цикл
    Сообщить("=======");
    рс.След();
КонецЦикла;

Проанализировав данную реализацию, выделим основные причины сбоя:

  1. Неверный провайдер соединения в объекте ODBCDatabase: указание OLEDB-провайдера Provider=SQLOLEDB.1 вместо ODBC-драйвера SQL Server приводит к тому, что метод ЕстьСоединение() возвращает 0.
  2. Отсутствие команды SET NOCOUNT ON: сервер MS SQL при выполнении промежуточных операций возвращает счетчики обработанных строк, что сбивает с толку ODBC-курсор 1С++.
  3. Ошибки и опечатки в псевдонимах полей: несовпадение имени выбираемого поля (например, ТКОстатки.МПЗМ вместо ТКОстатки.МПЗ) ломает структуру итоговой выборки.
  4. Занятость хэндла hstmt при обходе курсором: пошаговый обход большого набора данных с помощью рс.След() удерживает активный серверный курсор.

Шаг 1. Исправление строки подключения к базе данных

При использовании объекта ODBCDatabase необходимо формировать строку подключения с явным указанием драйвера ODBC, а не OLEDB-провайдера. Замените строку подключения на следующий синтаксис:


СтрокаПодключения = "DRIVER=SQL Server;Server=" + сервер + ";DataBase=" + БазаДанных + ";UID=" + Пользователь + ";Pwd=" + Пароль + ";";
бд.Соединение(СтрокаПодключения);

Если бд.ЕстьСоединение() = 0 Тогда
    Сообщить("Не удалось установить доп. соединение с базой данных!");
    Возврат;
КонецЕсли;

Шаг 2. Добавление инструкции SET NOCOUNT ON

Для корректной работы серверных курсоров и предотвращения выдачи ненужных служебных сообщений от MS SQL Server в самом начале SQL-запроса обязательна установка параметра SET NOCOUNT ON:


ТекстЗапроса = "SET NOCOUNT ON;
|SELECT TOP 2 
|    ТКОстатки.Комитент AS Комитент,
|    ТКОстатки.ЮридическоеЛицо AS ЮридическоеЛицо,
|    ТКОстатки.МестоХранения AS МестоХранения,
|    ТКОстатки.МПЗ AS МПЗ,
|    ТКОстатки.КоличествоОстаток AS КоличествоОстаток,
|    МестаХранения.ID AS МХМПЗ,
|    Контрагенты.ID AS ЮЛМПЗ
|FROM $РегистрОстатки.ТК(,,
|        (ВидОстатка = $Перечисление.ВидыОстатков.ОтклоненияБухУчета)
|        AND (Комитент = :Комитент),(Комитент,ЮридическоеЛицо,МестоХранения,МПЗ),(Количество)) AS ТКОстатки
|... ";

Шаг 3. Передача параметров и открытие набора записей

Если параметры задаются через объект MetaDataWork, их значение передается в текст SQL при обработке через ОбрМетаСКЛ(). Для открытия набора записей с явной передачей режимов работы рекомендуется использовать метод Открыть() с дополнительным параметром:


// Выполнение обработки текста запроса и замена метаимен
ТекстЗапроса = МД.ОбрМетаСКЛ(ТекстЗапроса);

// Открытие выборки в режиме чтения (параметр 1)
Если рс.Открыть(ТекстЗапроса, 1) = 0 Тогда
    Сообщить("Ошибка выполнения запроса: " + рс.ОписаниеОшибки());
    Возврат;
КонецЕсли;

Рекомендации по обработке больших объемов данных

Если запрос возвращает сотни тысяч или миллионы строк, пошаговый обход курсором рс.След() может вызывать блокировки ресурсов и ошибку занятости hstmt. Для решения этой задачи применимы следующие подходы:

  1. Выгрузка в Таблицу Значений: если объём памяти позволяет, вместо курсора используйте метод ВыгрузитьИнструкцию() или Выгрузить(), чтобы сразу освободить соединение на стороне SQL.
  2. Пагинация (порционная выборка): запрашивайте данные блоками (например, по 10 000–50 000 записей), с сортировкой по ID и условием WHERE ID > :ПоследнийID. Это полностью исключает зависание курсора.
  3. Диагностика через ОписаниеОшибки(): в случае сбоев всегда проверяйте результат метода рс.ОписаниеОшибки(), который возвращает точный текст ошибки от драйвера SQL Server.
← На главную