При интеграции 1С с внешними базами данных Microsoft SQL Server через прямые SQL-запросы разработчики часто сталкиваются с ситуацией, когда небольшие объемы данных записываются корректно, но при передаче объемных строк (например, сериализованных таблиц или больших текстовых дампов) операция аварийно завершается ошибкой:
[DBNETLIB][ConnectionWrite (send()).] General network error. Check your network documentation.
Разберем детально, почему возникает эта ошибка, какие системные ограничения действуют на стороне SQL Server и ADODB, а также рассмотрим практические способы решения этой проблемы.
Проанализируем технические факторы, приводящие к разрыву соединения:
65 536 * Network Packet Size. При стандартном размере сетевого пакета в 4 КБ предельный размер монолитного текста запроса составляет около 256 МБ. При попытке передать запрос длиной более 300 МБ SQL Server принудительно разрывает сетевую сессию.SQLOLEDB использует библиотеку dbnetlib.dll, которая не рассчитана на передачу непрерывных многомегабайтных пакетов в одном вызове.Посмотрим на источник данных в коде 1С. Использование метода ЗначениеВСтрокуВнутр() для больших таблиц порождает избыточный текст, размер которого многократно превышает реальный объем информации.
Рассмотрим, как уменьшить объем данных за счет перехода на бинарный формат со сжатием:
// Сериализуем данные в компактный бинарный вид со сжатием
ДвоичныеДанныеТЗ = ЗначениеВДвоичныеДанные(ТаблицаСтатистики);
СжатыеДанные = СжатиеДанных.Сжать(ДвоичныеДанныеТЗ);
// Сжатие позволяет сократить размер в 5-10 раз,
// переведя сотни мегабайт текста в компактный массив байтов
В самой таблице SQL Server для хранения таких данных следует использовать тип VARBINARY(MAX) вместо длинных строковых типов.
Выясним, как передавать данные без прямой конкатенации текста запроса. Использование объекта ADODB.Parameter выносит полезную нагрузку из синтаксического анализатора SQL, а метод AppendChunk позволяет отправлять данные частями (чанками).
Разберем по шагам реализацию через ADODB:
// Создаем подключение с использованием современного драйвера MSOLEDBSQL
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.ConnectionString = "Provider=MSOLEDBSQL;Data Source=SQLServerNode;Initial Catalog=TargetDB;Integrated Security=SSPI;";
Соединение.Open();
Команда = Новый COMОбъект("ADODB.Command");
Команда.ActiveConnection = Соединение;
Команда.CommandTimeout = 600;
// Текст запроса остается компактным и параметризованным
Команда.CommandText =
"MERGE dbo.Base_tab AS dst " +
"USING (SELECT ? AS id_query1c, ? AS id_querydp, ? AS bd, ? AS bin_tab) AS src " +
"ON (dst.id_query1c = src.id_query1c AND dst.id_querydp = src.id_querydp AND dst.bd = src.bd) " +
"WHEN MATCHED THEN UPDATE SET dst.bin_tab = src.bin_tab " +
"WHEN NOT MATCHED THEN INSERT (id_query1c, id_querydp, bd, bin_tab) VALUES (src.id_query1c, src.id_querydp, src.bd, src.bin_tab);";
// Создаем параметры (adInteger = 3, adLongVarWChar = 203)
Команда.Parameters.Append(Команда.CreateParameter("p1", 3, 1, , id_query1c));
Команда.Parameters.Append(Команда.CreateParameter("p2", 3, 1, , id_querydp));
Команда.Parameters.Append(Команда.CreateParameter("p3", 3, 1, , ИБ_id));
ПараметрДанных = Команда.CreateParameter("p4", 203, 1, СтрДлина(СтрокаДанных));
Команда.Parameters.Append(ПараметрДанных);
// Передаем значение частями (например, блоками по 1 МБ), избегая сетевого переполнения
РазмерБлока = 1048576;
Позиция = 1;
ДлинаВсего = СтрДлина(СтрокаДанных);
Пока Позиция <= ДлинаВсего Цикл
Кусок = Сред(СтрокаДанных, Позиция, РазмерБлока);
ПараметрДанных.AppendChunk(Кусок);
Позиция = Позиция + РазмерБлока;
КонецЦикла;
Команда.Execute();
Соединение.Close();
Если данные настолько велики, что их небезопасно передавать в оперативной памяти соединения, эффективным решением будет выгрузка во временный файл и чтение средствами самого SQL Server.
Проанализируем пример выполнения слияния через временный файл:
// 1. Сохраняем большой объем данных во временный файл, доступный серверу СУБД
ПутьКФайлу = "\\SharedServer\Exchange\temp_data_" + Строка(id_query1c) + ".dat";
ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ЗаписьТекста.Записать(СтрокаИзТЗ);
ЗаписьТекста.Закрыть();
// 2. В SQL Server считываем файл напрямую через OPENROWSET(BULK)
ТекстSQL =
"MERGE dbo.Base_tab AS dst " +
"USING ( " +
" SELECT " + Формат(id_query1c, "ЧГ=0") + " AS id_query1c, " +
" " + Формат(id_querydp, "ЧГ=0") + " AS id_querydp, " +
" " + Формат(ИБ_id, "ЧГ=0") + " AS bd, " +
" BulkColumn AS bin_tab " +
" FROM OPENROWSET(BULK N'" + ПутьКФайлу + "', SINGLE_NCLOB) AS src_data " +
") AS src " +
"ON (dst.id_query1c = src.id_query1c AND dst.id_querydp = src.id_querydp AND dst.bd = src.bd) " +
"WHEN MATCHED THEN UPDATE SET dst.bin_tab = src.bin_tab " +
"WHEN NOT MATCHED THEN INSERT (id_query1c, id_querydp, bd, bin_tab) VALUES (src.id_query1c, src.id_querydp, src.bd, src.bin_tab);";
Команда.CommandText = ТекстSQL;
Команда.Execute();
// 3. Удаляем временный файл после успешной обработки
УдалитьФайлы(ПутьКФайлу);
Для массовой вставки строк наиболее быстрым способом является использование класса SqlBulkCopy через .NET-мост платформы 1С или внешние библиотеки. Данный механизм работает в обход классического механизма разбора SQL-запросов и выполняет пакетную загрузку на уровне протокола TDS.
Разберем основные шаги реализации:
System.Data.DataTable.System.Data.SqlClient.SqlBulkCopy, связанный с целевой таблицей.WriteToServer(DataTable) данные отправляются на SQL-сервер с минимальными накладными расходами на память и сеть.Использование параметризации, сжатия бинарных данных или потоковой загрузки позволяет полностью снять ограничения на объем передаваемой информации и исключить сетевые ошибки DBNETLIB.