Как ускорить выгрузку данных из 1С в MySQL без медленного COM-соединения?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Интеграция решений на платформе 1С с интернет-магазинами и внешними сайтами на базе СУБД MySQL — классическая задача автоматизации торговой деятельности. Однако разработчики часто сталкиваются с критической проблемой: при попытке напрямую записать данные в удаленную базу MySQL через стандартный механизм COM-соединения и драйвер ODBC скорость импорта падает до неприемлемых значений. Например, выгрузка каталога из 13 500 товаров может занимать 15–20 минут и более.

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

В чем причина низкой скорости при работе через COM/ODBC?

Давайте выясним, почему прямое подключение из 1С к удаленной базе MySQL работает так медленно. Главный виновник здесь — не сам драйвер ODBC и не COM-технология как таковая, а архитектурная ошибка в реализации цикла записи. Рассмотрим типичный фрагмент кода, который часто используют разработчики:


Пока Выборка.Следующий() Цикл
    // ... подготовка данных ...
    Connection.Execute("INSERT INTO tested SET name = '" + Наименование + "', price = '" + Цена + "'");
КонецЦикла;

При таком подходе на каждую из 13 500 строк номенклатуры программа выполняет отдельное обращение к серверу БД. Если сервер MySQL находится на удаленном хостинге (в интернете), то на каждый единичный запрос накладывается сетевая задержка (RTT — Round Trip Time). Даже при отличной связи и задержке в 20 миллисекунд только на сетевое ожидание для 13 500 запросов уйдет:

13 500 * 0.02 сек = 270 секунд (4.5 минуты) чистого ожидания сети!

Добавьте к этому накладные расходы на парсинг каждого SQL-запроса сервером MySQL, транзакционные издержки (сброс логов на диск при каждой операции) и накладные расходы самого COM-соединения. В итоге мы получаем крайне низкую производительность. Ниже мы детально разберем пять способов решения этой проблемы.

Решение 1. Пакетная вставка данных (Batch Insert) — самый простой способ оптимизации

Если архитектура решения требует прямого подключения к MySQL, мы можем кардинально сократить количество сетевых запросов, объединив множество одиночных инструкций INSERT в один пакетный запрос. СУБД MySQL поддерживает синтаксис множественной вставки: INSERT INTO table (col1, col2) VALUES (val1, val2), (val3, val4), ...;.

Рассмотрим, как реализовать пакетную запись порциями по 50–100 элементов. Это позволит сократить количество обращений к базе данных с 13 500 до 135–270 запросов.


Connection = Новый COMОбъект("ADODB.Connection");
// Установка соединения с MySQL...

CommandSQL = Новый COMОбъект("ADODB.Command");
CommandSQL.ActiveConnection = Connection;

РазмерПорции = 100; // Оптимальный размер пакета для баланса скорости и объема памяти
ШапкаЗапроса = "INSERT INTO tested (name, article, quantity, price) VALUES ";
ШаблонСтрокиЗапроса = "('%1','%2','%3','%4'),";
НомерСтроки = 1;
РазмерВыборки = Выборка.Количество(); // Получаем общее число записей
ТекстЗапроса = ШапкаЗапроса;

Пока Выборка.Следующий() Цикл
    Наименование = Строка(Выборка.Номенклатура);
    // Экранируем одинарные кавычки во избежание синтаксических ошибок SQL
    Наименование = СтрЗаменить(Наименование, "'", "''"); 
    Артикул = Строка(Выборка.Артикул);
    Количество = Число(Выборка.Остаток);
    Цена = Формат(Выборка.Цена, "ЧГ=");
    
    // Формируем очередную строку значений
    ТекстЗапроса = ТекстЗапроса + Символы.ПС + СтрШаблон(ШаблонСтрокиЗапроса, Наименование, Артикул, Количество, Цена);
    
    Если (НомерСтроки % РазмерПорции = 0) ИЛИ (НомерСтроки >= РазмерВыборки) Тогда
        // Заменяем последнюю запятую на точку с запятой для завершения запроса
        ТекстЗапроса = Лев(ТекстЗапроса, СтрДлина(ТекстЗапроса) - 1) + ";"; 
        CommandSQL.CommandText = ТекстЗапроса;
        Попытка
            CommandSQL.Execute();
        Исключение
            ЗаписьЖурналаРегистрации("Ошибка записи в MySQL", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
        КонецПопытки;
        
        // Сброс буфера для следующей порции
        ТекстЗапроса = ШапкаЗапроса;
    КонецЕсли;
    
    НомерСтроки = НомерСтроки + 1;
КонецЦикла;

Этот простой шаг сократит время выполнения операции с 16 минут до 30–60 секунд, поскольку сетевые задержки практически перестанут влиять на общий процесс выгрузки.

Решение 2. Оптимизация исходного запроса на стороне 1С

Часто медленная работа выгрузки связана со сложными, неоптимальными запросами в самой 1С. Вложенные виртуальные таблицы «на лету» без использования временных таблиц заставляют СУБД 1С строить неоптимальные планы выполнения. Давайте проанализируем и оптимизируем исходный запрос.

Вместо громоздкого вложенного запроса с избыточными группировками применим классическое левое соединение виртуальной таблицы остатков и среза последних цен:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Остатки.Номенклатура КАК Номенклатура,
|	Остатки.Номенклатура.Наименование КАК Наименование,
|	Остатки.Номенклатура.Артикул КАК Артикул,
|	Остатки.Номенклатура.Производитель КАК Производитель,
|	Остатки.КоличествоОстаток КАК Остаток,
|	ЕСТЬNULL(Цены.Цена, 0) * 1.33 КАК Цена
|ИЗ
|	РегистрНакопления.ТоварыНаСкладах.Остатки(
|		,
|		КоличествоОстаток > 0
|	) КАК Остатки
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
|		,
|		ТипЦен = &ТипЦенРозница
|	) КАК Цены
|	ПО Остатки.Номенклатура = Цены.Номенклатура
|ГДЕ
|	ЕСТЬNULL(Цены.Цена, 0) > 0";

Запрос.УстановитьПараметр("ТипЦенРозница", Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Закупочная продажа", Истина));
Выборка = Запрос.Выполнить().Выбрать();

Почему этот запрос эффективнее? Мы избавились от тройной вложенности и избыточных группировок СУММА() по полям, которые по своей природе уникальны или уже сгруппированы в виртуальных таблицах. Такой запрос отработает на сервере 1С за доли секунды.

Решение 3. Использование команды LOAD DATA LOCAL INFILE (Максимальная скорость)

Если вам требуется абсолютно мгновенный импорт (буквально за 1–2 секунды для десятков тысяч строк), то лучшим выбором будет использование встроенного механизма MySQL для загрузки текстовых файлов. Команда LOAD DATA LOCAL INFILE оптимизирована на уровне ядра СУБД и работает в сотни раз быстрее любых вариантов INSERT.

Разберем по шагам эту схему работы:

  1. 1С формирует локальный CSV или текстовый файл с разделителями (например, табуляцией), куда записывает все выгружаемые товары.
  2. Через COM-соединение к MySQL отправляется одна короткая команда, указывающая серверу СУБД прочитать подготовленный файл и импортировать его структуру в нужную таблицу.

Посмотрим на пример кода реализации данного метода в 1С:


// Шаг 1. Формируем локальный текстовый файл на диске сервера 1С
ПутьКФайлу = "C:\temp\export_goods.csv";
ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);

Пока Выборка.Следующий() Цикл
    СтрокаДанных = Строка(Выборка.Номенклатура) + ";" + 
                   Строка(Выборка.Артикул) + ";" + 
                   Формат(Выборка.Остаток, "ЧГ=") + ";" + 
                   Формат(Выборка.Цена, "ЧГ=");
    ЗаписьТекста.ЗаписатьСтроку(СтрокаДанных);
КонецЦикла;
ЗаписьТекста.Закрыть();

// Шаг 2. Загружаем файл в базу данных MySQL одним запросом
// Обратите внимание на использование двойных обратных слэшей в пути для MySQL
ПутьКФайлуSQL = СтрЗаменить(ПутьКФайлу, "\", "/"); 

SQLКоманда = "LOAD DATA LOCAL INFILE '" + ПутьКФайлуSQL + "' " +
             "INTO TABLE tested " +
             "FIELDS TERMINATED BY ';' " +
             "LINES TERMINATED BY '\r\n' " +
             "(name, article, quantity, price);";

Connection.Execute(SQLКоманда);

Важное примечание: Для работы этого метода в настройках сервера MySQL (файл конфигурации my.cnf или my.ini) должен быть включен параметр local_infile = 1, а также соответствующая опция должна быть активирована на стороне клиента (в строке подключения ODBC-драйвера).

Решение 4. HTTP-соединение (Интеграция через веб-сервис на сайте)

Прямое подключение к базе данных сайта из внешней сети через порт 3306 создает колоссальную угрозу безопасности («голая жопа» в интернет, как выразились участники обсуждения). Любой злоумышленник может попытаться подобрать пароль к СУБД или использовать уязвимости в коде базы данных.

Наиболее надежный, безопасный и современный подход — закрыть порт MySQL для внешних подключений, а обмен выполнять через HTTP-сервис (API-эндпоинт), написанный на стороне сайта (например, на PHP, Python или Node.js). 1С в данном сценарии выступает в роли HTTP-клиента.

Рассмотрим по шагам этот процесс:

  1. 1С собирает данные в массив структур и сериализует их в формат JSON или XML. На старых версиях платформы (1С 8.2) для формирования JSON можно использовать сторонние библиотеки или объект ЗаписьJSON (если используется режим совместимости с 8.3).
  2. 1С отправляет POST-запрос сжатого пакета данных на веб-сервер сайта.
  3. Скрипт на стороне сайта (например, на PHP) принимает JSON-пакет, декодирует его и осуществляет локальную запись в СУБД MySQL. Локальные запросы внутри сервера сайта выполняются мгновенно, так как задержка сети между веб-сервером и СУБД равна нулю.

Посмотрим на пример отправки данных из 1С с помощью объекта HTTPСоединение:


// Формируем тело запроса в формате JSON (упрощенный пример)
ТекстJSON = "[";
Пока Выборка.Следующий() Цикл
    ТекстJSON = ТекстJSON + "{" +
                """name"":""" + СтрЗаменить(Строка(Выборка.Номенклатура), """", "\""") + """," +
                """article"":""" + Строка(Выборка.Артикул) + """," +
                """quantity"":" + Формат(Выборка.Остаток, "ЧГ=") + "," +
                """price"":" + Формат(Выборка.Цена, "ЧГ=") + "},";
КонецЦикла;
// Убираем хвостовую запятую и закрываем массив
ТекстJSON = Лев(ТекстJSON, СтрДлина(ТекстJSON) - 1) + "]";

// Настраиваем HTTP-соединение
СерверСайта = "mysite.ru";
HTTPСоединение = Новый HTTPСоединение(СерверСайта, , , , , 30); // Таймаут 30 секунд

HTTPЗапрос = Новый HTTPЗапрос("/api/import_goods.php");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос.УстановитьТелоИзСтроки(ТекстJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Попытка
    HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
    Если HTTPОтвет.КодСостояния = 200 Тогда
        Сообщить("Импорт успешно завершен! Ответ сервера: " + HTTPОтвет.ПолучитьТелоКакСтроку());
    Иначе
        Сообщить("Ошибка импорта. Код состояния сервера: " + HTTPОтвет.КодСостояния);
    КонецЕсли;
Исключение
    Сообщить("Не удалось установить связь с сервером: " + ОписаниеОшибки());
КонецПопытки;

Решение 5. Самый надежный «дедовский» метод — FTP-обмен

Если вы хотите максимально изолировать систему 1С от сайта и гарантировать стабильную работу даже при нестабильном интернет-соединении, используйте выгрузку через промежуточный FTP-сервер. Этот метод прост в реализации и абсолютно надежен.

Схема обмена выглядит следующим образом:

  1. 1С формирует CSV-файл или XML-пакет на локальном диске.
  2. С помощью встроенного механизма 1С FTPСоединение файл копируется на FTP-сервер сайта.
  3. На сайте настраивается планировщик задач (Cron), который раз в 5–10 минут проверяет наличие файла в определенной папке. При обнаружении нового файла скрипт парсит его, импортирует данные в MySQL на локальной скорости и удаляет отработанный файл.

Этот способ полностью убирает зависимость 1С от доступности базы данных MySQL «здесь и сейчас», а также решает любые проблемы с производительностью на стороне клиента 1С.

Резюме по выбору архитектуры решения

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

← На главную