При переходе информационных баз 1С с файлового режима на клиент-серверный (SQL Server) пользователи часто сталкиваются со значительным падением производительности отчетов, особенно если для взаимодействия между базами использовались COM-соединения. Давайте подробно разберем, почему это происходит, как диагностировать проблему и какие решения существуют для ускорения работы.
Изначально, когда базы функционировали в файловом режиме, все операции, включая запросы и обработку данных, выполнялись локально в рамках одной машины. Это обеспечивало высокую скорость, поскольку не было накладных расходов на сетевое взаимодействие и работу сервера приложений. Однако, после перевода баз на SQL Server, ситуация кардинально меняется:
V8.Connect()), по сути имитируют работу интерактивного пользователя. Это может приводить к значительному объему сетевого трафика, если мы передаем большие объемы данных или выполняем много мелких операций в цикле. Мы наблюдали, что время подключения через COM к SQL-базам может увеличиваться в 2-3 раза (с 5 до 15 секунд) по сравнению с файловыми базами, а это уже существенная задержка, которая может превышать время выполнения самого запроса.Прежде чем приступать к оптимизации, нам необходимо точно определить, где именно происходит замедление. Для этого мы рекомендуем использовать следующие инструменты и подходы:
Connection = V8.Connect(СтрокаПодключения)). Часто именно этот этап занимает до 50% и более общего времени.Используем замеры для сравнения производительности на файловых и клиент-серверных базах, чтобы выявить, какие операции стали выполняться значительно медленнее.
Часто одной из ключевых причин замедления является неоптимальное составление запросов, которые хорошо работали на файловой базе, но становятся "тяжелыми" на SQL Server. Рассмотрим примеры и рекомендации.
В нашем случае, одним из проблемных мест оказался запрос с полным соединением и объединением, который был представлен следующим образом:
ВЫБРАТЬ
ЕСТЬNULL(ХозрасчетныйОстатки.Субконто1, ХозрасчетныйОбороты.Субконто1) КАК Контрагент,
ЕСТЬNULL(ХозрасчетныйОстатки.Субконто1.Родитель.Наименование, ХозрасчетныйОбороты.Субконто1.Родитель.Наименование) КАК Родитель,
ЕСТЬNULL(ХозрасчетныйОстатки.Субконто1.Наименование, ХозрасчетныйОбороты.Субконто1.Наименование) КАК Наименование,
ЕСТЬNULL(ХозрасчетныйОстатки.Субконто1.ИНН, ХозрасчетныйОбороты.Субконто1.ИНН) КАК ИНН,
ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстатокДт, 0) КАК НамДолжны,
ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстатокКт, 0) КАК МыДолжны,
ЕСТЬNULL(ХозрасчетныйОбороты.СуммаОборотДт, 0) КАК Оплата,
0 КАК Сальдо_1007
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет В ИЕРАРХИИ (&СписокСчетов), , ) КАК ХозрасчетныйОстатки
ПОЛНОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(&ОплатаС, &ОплатаПо, , Счет В ИЕРАРХИИ (&СписокСчетов), , , КорСчет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетныеСчета), ) КАК ХозрасчетныйОбороты
ПО ХозрасчетныйОстатки.Субконто1 = ХозрасчетныйОбороты.Субконто1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Хозрасчетный_1007.Субконто1,
Хозрасчетный_1007.Субконто1.Родитель,
Хозрасчетный_1007.Субконто1.Наименование,
Хозрасчетный_1007.Субконто1.ИНН,
0,
0,
0,
ЕСТЬNULL(Хозрасчетный_1007.СуммаОстаток, 0)
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.МатериалыПереданныеВПереработку), , ) КАК Хозрасчетный_1007
SQL Server, особенно старых версий, часто неоптимально обрабатывает полные соединения (FULL JOIN) и сложные вложенные запросы (SELECT FROM SELECT). Планировщик может выбирать неэффективные планы, что приводит к многократному чтению данных и зависаниям.
Один из самых эффективных способов оптимизации сложных запросов — это разбиение их на более простые части с использованием временных таблиц. Это позволяет SQL Server строить более эффективные планы выполнения и, что важно, индексировать временные таблицы по соединяемым полям.
Рассмотрим, как мы можем переписать проблемный запрос с использованием временных таблиц:
// Временная таблица для остатков
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОстатки.Субконто1 КАК Контрагент,
| ХозрасчетныйОстатки.Субконто1.Родитель.Наименование КАК Родитель,
| ХозрасчетныйОстатки.Субконто1.Наименование КАК Наименование,
| ХозрасчетныйОстатки.Субконто1.ИНН КАК ИНН,
| ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстатокДт, 0) КАК НамДолжны,
| ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстатокКт, 0) КАК МыДолжны
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет В ИЕРАРХИИ (&СписокСчетов), , ) КАК ХозрасчетныйОстатки";
Запрос.УстановитьПараметр("НаДату", Граница);
Запрос.УстановитьПараметр("СписокСчетов", СписокСчетов);
Запрос.Выполнить();
// Временная таблица для оборотов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОбороты.Субконто1 КАК Контрагент,
| ЕСТЬNULL(ХозрасчетныйОбороты.СуммаОборотДт, 0) КАК Оплата
|ПОМЕСТИТЬ ВТ_Обороты
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(&ОплатаС, &ОплатаПо, , Счет В ИЕРАРХИИ (&СписокСчетов), , , КорСчет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетныеСчета), ) КАК ХозрасчетныйОбороты";
Запрос.УстановитьПараметр("ОплатаС", Отчет.ОплатаС);
Запрос.УстановитьПараметр("ОплатаПо", КонецДня(Отчет.ОплатаПо));
Запрос.УстановитьПараметр("СписокСчетов", СписокСчетов);
Запрос.Выполнить();
// Временная таблица для счета 10.07
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Хозрасчетный_1007.Субконто1 КАК Контрагент,
| ЕСТЬNULL(Хозрасчетный_1007.СуммаОстаток, 0) КАК Сальдо_1007
|ПОМЕСТИТЬ ВТ_1007
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&НаДату, Счет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.МатериалыПереданныеВПереработку), , ) КАК Хозрасчетный_1007";
Запрос.УстановитьПараметр("НаДату", Граница);
Запрос.Выполнить();
// Финальный запрос, объединяющий данные
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ВТ_Остатки.Контрагент, ВТ_Обороты.Контрагент) КАК Контрагент,
| ЕСТЬNULL(ВТ_Остатки.Родитель, "") КАК Родитель,
| ЕСТЬNULL(ВТ_Остатки.Наименование, "") КАК Наименование,
| ЕСТЬNULL(ВТ_Остатки.ИНН, "") КАК ИНН,
| ЕСТЬNULL(ВТ_Остатки.НамДолжны, 0) КАК НамДолжны,
| ЕСТЬNULL(ВТ_Остатки.МыДолжны, 0) КАК МыДолжны,
| ЕСТЬNULL(ВТ_Обороты.Оплата, 0) КАК Оплата,
| ЕСТЬNULL(ВТ_1007.Сальдо_1007, 0) КАК Сальдо_1007
|ИЗ
| ВТ_Остатки КАК ВТ_Остатки
| ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ВТ_Обороты
| ПО ВТ_Остатки.Контрагент = ВТ_Обороты.Контрагент
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_1007 КАК ВТ_1007
| ПО ЕСТЬNULL(ВТ_Остатки.Контрагент, ВТ_Обороты.Контрагент) = ВТ_1007.Контрагент";
РезультатЗапроса = Запрос.Выполнить();
Такой подход позволил значительно снизить время выполнения запроса на сервере, превратив зависания в приемлемые 25 секунд на проблемном сервере и 4 секунды на тестовом.
Регулярное обслуживание SQL Server критически важно для поддержания производительности. Мы должны убедиться, что выполняются следующие операции:
Хотя оптимизация запросов и SQL Server может значительно помочь, COM-соединение само по себе может быть узким местом из-за своей природы. Рассмотрим более современные и эффективные альтернативы:
Эти механизмы являются предпочтительными для межпрограммного взаимодействия в распределенных системах 1С. Они основаны на протоколе HTTP(S) и позволяют:
Для реализации HTTP-сервиса нам потребуется создать новый HTTP-сервис в конфигурации или расширении, добавить в него метод, который будет принимать параметры (например, текст запроса и его параметры) и возвращать результат в виде сериализованной таблицы значений.
Пример использования HTTP-сервиса:
// На стороне удаленной базы (в HTTP-сервисе)
Функция ВыполнитьЗапросОтчета(ПараметрыЗапроса) Экспорт
// Десериализуем полученные параметры (текст запроса, параметры запроса)
ТекстЗапроса = ПараметрыЗапроса.ТекстЗапроса;
Параметры = ПараметрыЗапроса.Параметры;
Запрос = Новый Запрос(ТекстЗапроса);
Для Каждого Параметр Из Параметры Цикл
Запрос.УстановитьПараметр(Параметр.Ключ, Параметр.Значение);
КонецЦикла;
Результат = Запрос.Выполнить().Выгрузить();
Возврат СериализаторXDTO.ЗаписатьXML(Результат); // Возвращаем сериализованный результат
КонецФункции
// На стороне основной базы (клиент)
Процедура ПолучитьДанныеЧерезHTTP()
// Создаем структуру для передачи параметров
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("ТекстЗапроса", "ВЫБРАТЬ ..."); // Ваш SQL-запрос
ПараметрыЗапроса.Вставить("Параметры", Новый Соответствие); // Параметры запроса
// Сериализуем параметры
СериализованныеПараметры = СериализаторXDTO.ЗаписатьXML(ПараметрыЗапроса);
// Отправляем HTTP-запрос
HTTPЗапрос = Новый HTTPЗапрос("/e1c/hs/MyService/ExecuteQuery");
HTTPЗапрос.УстановитьТелоИзСтроки(СериализованныеПараметры, КодировкаТекста.UTF8, "application/xml");
HTTPСоединение = Новый HTTPСоединение("localhost", 80, , , , 100); // Настройки соединения
Ответ = HTTPСоединение.Отправить(HTTPЗапрос);
Если Ответ.Код = 200 Тогда
СтрокаРезультата = Ответ.ПолучитьТелоКакСтроку();
Результат = СериализаторXDTO.ПрочитатьXML(СтрокаРезультата);
// Обрабатываем полученную таблицу значений
Иначе
Сообщить("Ошибка HTTP-запроса: " + Ответ.Код + " " + Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
КонецПроцедуры
ODATA — это протокол для построения и использования RESTful API. 1С поддерживает публикацию стандартного интерфейса ODATA, который позволяет получать данные из справочников, документов и других объектов метаданных без существенной доработки конфигурации, при условии, что эти объекты были опубликованы для ODATA. Это может быть очень быстрым решением для получения простых данных, но для сложных, агрегированных отчетов может потребовать дополнительной настройки или комбинирования с другими методами.
Для публикации объектов через ODATA, мы можем использовать метод УстановитьСоставСтандартногоИнтерфейсаOData(), добавляя в массив нужные объекты метаданных:
// Пример на стороне удаленной базы (в общем модуле или регламентном задании)
Массив = Новый Массив;
Массив.Добавить(Метаданные.Справочники.Контрагенты);
Массив.Добавить(Метаданные.Справочники.Номенклатура);
Массив.Добавить(Метаданные.Справочники.ВидыНоменклатуры); // Добавляем нужный справочник
УстановитьСоставСтандартногоИнтерфейсаOData(Массив);
После публикации мы можем обращаться к данным по URL вида http://localhost/intertopKAmarat/odata/standard.odata/Catalog_ВидыНоменклатуры.
Если актуальность данных в отчете не требуется в режиме "онлайн" (допустима задержка в несколько минут или часов), мы можем рассмотреть создание отдельной базы-агрегатора. Механизм работы такой базы может быть следующим:
Если мы все же вынуждены использовать COM-соединение, то для повышения скорости передачи больших объемов данных мы можем сериализовать таблицы значений в строку (например, XML или JSON) на удаленной стороне, передать эту строку через COM, а затем десериализовать на основной стороне. Это делает передачу атомарной – по сети передается одна строка, а не множество объектов, что значительно быстрее.
В ходе нашего анализа стало очевидным, что версия SQL Server и характеристики аппаратного обеспечения сервера играют критическую роль в производительности 1С.
Мы настоятельно рекомендуем проводить комплексные тесты производительности сервера (CPU-Z, CrystalDiskMark) для оценки его реальной пригодности для работы 1С в клиент-серверном режиме.
Ускорение работы с базами 1С через COM-соединение после перехода на SQL Server — это комплексная задача, требующая анализа и оптимизации на нескольких уровнях. Мы убедились, что проблему можно решить, применяя следующие шаги:
Применение этих рекомендаций поможет нам значительно ускорить работу отчетов и обеспечить стабильную и комфортную работу пользователей в клиент-серверных базах 1С.