При использовании внешней компоненты 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 Цикл
Сообщить("=======");
рс.След();
КонецЦикла;
Проанализировав данную реализацию, выделим основные причины сбоя:
Provider=SQLOLEDB.1 вместо ODBC-драйвера SQL Server приводит к тому, что метод ЕстьСоединение() возвращает 0.ТКОстатки.МПЗМ вместо ТКОстатки.МПЗ) ломает структуру итоговой выборки.рс.След() удерживает активный серверный курсор.При использовании объекта ODBCDatabase необходимо формировать строку подключения с явным указанием драйвера ODBC, а не OLEDB-провайдера. Замените строку подключения на следующий синтаксис:
СтрокаПодключения = "DRIVER=SQL Server;Server=" + сервер + ";DataBase=" + БазаДанных + ";UID=" + Пользователь + ";Pwd=" + Пароль + ";";
бд.Соединение(СтрокаПодключения);
Если бд.ЕстьСоединение() = 0 Тогда
Сообщить("Не удалось установить доп. соединение с базой данных!");
Возврат;
КонецЕсли;
Для корректной работы серверных курсоров и предотвращения выдачи ненужных служебных сообщений от 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 ТКОстатки
|... ";
Если параметры задаются через объект MetaDataWork, их значение передается в текст SQL при обработке через ОбрМетаСКЛ(). Для открытия набора записей с явной передачей режимов работы рекомендуется использовать метод Открыть() с дополнительным параметром:
// Выполнение обработки текста запроса и замена метаимен
ТекстЗапроса = МД.ОбрМетаСКЛ(ТекстЗапроса);
// Открытие выборки в режиме чтения (параметр 1)
Если рс.Открыть(ТекстЗапроса, 1) = 0 Тогда
Сообщить("Ошибка выполнения запроса: " + рс.ОписаниеОшибки());
Возврат;
КонецЕсли;
Если запрос возвращает сотни тысяч или миллионы строк, пошаговый обход курсором рс.След() может вызывать блокировки ресурсов и ошибку занятости hstmt. Для решения этой задачи применимы следующие подходы:
ВыгрузитьИнструкцию() или Выгрузить(), чтобы сразу освободить соединение на стороне SQL.ID и условием WHERE ID > :ПоследнийID. Это полностью исключает зависание курсора.рс.ОписаниеОшибки(), который возвращает точный текст ошибки от драйвера SQL Server.