При интеграции 1С с внешними базами данных MS SQL Server посредством технологии ADO (ActiveX Data Objects) разработчики часто сталкиваются с неприятной проблемой. При выполнении сложных, «тяжелых» запросов система аварийно завершает работу с ошибкой: «Microsoft OLE DB Provider for SQL Server: Время ожидания запроса истекло». При этом ошибка стабильно воспроизводится ровно через 30 секунд после начала выполнения запроса.
Самое интересное заключается в том, что в коде 1С могут быть явно прописаны свойства таймаутов, например, со значениями 300000 или больше. Но драйвер упорно игнорирует эти параметры. В этой статье мы подробно разберем, почему так происходит, выясним скрытые технические причины такого поведения и настроим стабильное подключение без ограничения времени ожидания. Для ознакомления с базовыми правилами и примерами работы с внешними базами пригодится руководство по подключению и получению данных из SQL в 1С.
Рассмотрим код, который часто используют разработчики для инициализации соединения и отправки запроса к СУБД:
Connection = Новый COMОбъект("ADODB.Connection");
Connection.ConnectionTimeout = 300000;
Connection.CommandTimeout = 300000;
Connection.Open(СтрокаПодключения);
Command = Новый COMОбъект("ADODB.Command");
Command.CommandTimeout = 300000;
Command.CommandType = 1;
Command.ActiveConnection = Connection;
На первый взгляд, все сделано правильно. Заданы таймауты как для самого соединения (ConnectionTimeout), так и для объекта команды (CommandTimeout). Тем не менее запрос все равно завершается ошибкой ровно через полминуты. Давайте по шагам проанализируем, где кроется системная ошибка.
Свойства таймаута настраиваются для конкретных объектов COM. Однако решающее значение имеет то, каким образом и через какой объект запускается выполнение SQL-кода. Выделим два наиболее частых сценария, приводящих к сбросу настроек:
Command.Execute(ТекстЗапроса);
или так:
Connection.Execute(ТекстЗапроса);
в этот момент ADO игнорирует все предварительно настроенные свойства вашего объекта Command (включая CommandTimeout). Провайдер создает временный, скрытый внутренний объект команды со стандартными настройками по умолчанию. И, разумеется, дефолтный таймаут для него равен ровно 30 секундам.
Recordset.Open(ТекстЗапроса, Connection);
В данном случае объект Recordset связывается напрямую с соединением, минуя настроенный объект Command. Внутренний механизм выполнения запроса опять инициализирует стандартный таймаут в 30 секунд.
Правильное решение: Чтобы принудительно заставить ADO использовать ваши настройки таймаута, необходимо предварительно присвоить текст SQL-запроса свойству CommandText объекта Command, а затем выполнить метод Execute абсолютно без параметров, либо передать сам объект Command в качестве источника данных для Recordset.
Проанализируем правильный шаблон вызова:
// Назначаем текст запроса объекту Command
Command.CommandText = ТекстЗапроса;
// Вариант 1: Если данные возвращать не нужно (например, UPDATE, INSERT)
Command.Execute();
// Вариант 2: Если нам нужно получить результат выборки (SELECT)
Recordset = Новый COMОбъект("ADODB.Recordset");
Recordset.Open(Command);
Обратите внимание на значение, указанное в исходном коде: 300000. Разработчики 1С привыкли, что большинство системных функций платформы (например, в фоновых заданиях или методах ожидания) принимают параметры времени в миллисекундах.
Однако в технологии ADODB параметры ConnectionTimeout и CommandTimeout измеряются в секундах!
Установив значение 300000, вы указали время ожидания, равное 300 000 секунд. Это составляет чуть меньше 3,5 суток непрерывного ожидания. Передавая такое колоссальное число, мы можем столкнуться со следующими проблемами:
Правильное решение: Для установки бесконечного времени ожидания (хотя на практике это не рекомендуется делать во избежание вечных зависаний процессов) используется значение 0. Если вам нужно установить таймаут, например, на 10 минут, укажите адекватное число в секундах:
Command.CommandTimeout = 600; // 10 минут ожидания
В строке подключения к СУБД ключевую роль играет параметр Provider. Текст ошибки из заголовка прямо указывает на использование Microsoft OLE DB Provider for SQL Server (имя провайдера в строке подключения — SQLOLEDB).
Этот провайдер официально признан Microsoft устаревшим и небезопасным. Он не развивается и не поддерживает новые стандарты протоколов, включая TLS 1.2, а также специфические типы данных современных версий MS SQL Server. В современных операционных системах и связках с новыми СУБД он работает нестабильно и может самопроизвольно сбрасывать настройки COM-соединения, игнорируя передаваемые таймауты.
Правильное решение: Замените устаревший провайдер в строке подключения на современный драйвер.
Provider=SQLOLEDB; используйте современный провайдер Microsoft OLE DB Driver for SQL Server: Provider=MSOLEDBSQL;.Provider=SQLNCLI11;.Если после исправления кода и замены провайдера таймаут все равно срабатывает на определенной секунде, проблема может крыться на стороне самого SQL-сервера. Рассмотрим две основные системные причины:
LOCK_TIMEOUT), сервер принудительно прерывает транзакцию и возвращает клиенту ошибку таймаута. При этом настройки клиентского CommandTimeout игнорируются, так как инициатором разрыва выступает СУБД.Для минимизации блокировок при чтении данных из 1С рекомендуется использовать в SQL-запросах указание «грязного чтения» (NOLOCK):
SELECT * FROM dbo._InfoReg123 WITH (NOLOCK) WHERE ...
Сведем все рекомендации воедино и напишем надежный, отказоустойчивый шаблон выполнения прямого SQL-запроса из 1С с корректной обработкой времени ожидания:
// Инициализируем соединение
Connection = Новый COMОбъект("ADODB.Connection");
// Время ожидания установления соединения (в секундах)
Connection.ConnectionTimeout = 60;
// Формируем безопасную строку подключения с современным провайдером MSOLEDBSQL
СтрокаПодключения = "Provider=MSOLEDBSQL;Data Source=SQLServerName;Initial Catalog=BaseName;User ID=sa;Password=MySuperPassword;";
Попытка
Connection.Open(СтрокаПодключения);
Исключение
ВызватьИсключение "Не удалось подключиться к СУБД: " + ОписаниеОшибки();
КонецПопытки;
// Инициализируем команду
Command = Новый COMОбъект("ADODB.Command");
Command.ActiveConnection = Connection;
Command.CommandType = 1; // adCmdText (прямой SQL-текст)
// Устанавливаем разумный таймаут выполнения запроса (например, 15 минут)
// Если нужен бесконечный таймаут, ставим 0
Command.CommandTimeout = 900;
// Назначаем текст запроса свойству объекта Command
Command.CommandText = "
|SELECT TOP 100
| _IDRRef,
| _Fld1234
|FROM
| dbo._Document150 WITH (NOLOCK)
|ORDER BY
| _Date_Time DESC";
// Выполняем запрос через объект Command для сохранения настроек таймаута
Recordset = Новый COMОбъект("ADODB.Recordset");
Попытка
Recordset.Open(Command);
Исключение
Connection.Close();
ВызватьИсключение "Ошибка выполнения SQL-запроса: " + ОписаниеОшибки();
КонецПопытки;
// Обрабатываем полученный Recordset
Пока Recordset.EOF() = 0 Цикл
// ... логика обработки данных ...
Recordset.MoveNext();
КонецЦикла;
// Закрываем соединения
Recordset.Close();
Connection.Close();
Используя данный подход, вы гарантируете, что настройки таймаута будут корректно транслированы на уровень OLE DB драйвера, СУБД не сбросит соединение раньше времени, а сам сеанс взаимодействия между 1С и MS SQL Server будет работать стабильно и предсказуемо. Если требуется готовое решение для обмена без написания своего кода, подойдёт подсистема выгрузки данных во внешнюю базу SQL.