В процессе разработки и эксплуатации крупных систем на базе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой замедления работы отчетов и проведения документов. Одной из ключевых причин является неоптимальная работа с базой данных через механизм запросов. В данной статье мы подробно проанализируем основные методы оптимизации, разберем типичные ошибки и научимся писать код, который будет работать эффективно даже на огромных массивах данных.
Прежде чем приступать к исправлению кода, давайте проанализируем ситуацию и выясним, как 1С взаимодействует с СУБД (MS SQL Server, PostgreSQL и др.). Когда мы пишем запрос на языке 1С, платформа транслирует его в SQL-запрос. Если запрос составлен некорректно, СУБД не может эффективно использовать индексы, что приводит к полному сканированию таблиц (Table Scan). Это критически замедляет систему при росте объема данных.
Рассмотрим основные факторы, влияющие на производительность:
ГДЕ для фильтрации данных виртуальных таблиц вместо использования параметров этих таблиц.Одним из самых эффективных способов ускорения сложных запросов является использование объекта МенеджерВременныхТаблиц. Разберем по шагам, почему это работает лучше, чем вложенные запросы. Вложенный запрос выполняется СУБД динамически, и оптимизатору SQL бывает сложно построить эффективный план его выполнения. Временная таблица — это физическая таблица в базе данных (в tempdb), которую можно проиндексировать.
Посмотрим на пример правильной работы с временными таблицами. Предположим, нам нужно получить данные о продажах по списку определенных номенклатурных позиций. Проанализируем следующий подход:
// Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Шаг 1: Помещаем список товаров во временную таблицу
Запрос.Текст =
"ВЫБРАТЬ
| СписокТоваров.Ссылка КАК Товар
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &СписокТоваров КАК СписокТоваров
|ИНДЕКСИРОВАТЬ ПО
| Товар";
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);
Запрос.Выполнить();
// Шаг 2: Используем временную таблицу для соединения с данными продаж
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Номенклатура,
| ПродажиОбороты.КоличествоОборот
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ПродажиОбороты
| ПО ВТ_Товары.Товар = ПродажиОбороты.Номенклатура";
Результат = Запрос.Выполнить().Выгрузить();
Обратите внимание на ключевое слово ИНДЕКСИРОВАТЬ ПО. Это критически важный момент. Без индексации временной таблицы соединение с ней при большом количестве записей будет крайне медленным. Мы создаем индекс по полю Товар, что позволяет СУБД мгновенно находить нужные записи при соединении.
Проанализируем распространенную ошибку: наложение условий на виртуальную таблицу через конструкцию ГДЕ. Когда мы пишем ГДЕ после обращения к виртуальной таблице, 1С сначала получает все данные из таблицы (например, все обороты за период), а затем фильтрует их. Если же мы передаем условие в параметры виртуальной таблицы, фильтрация происходит на уровне СУБД в момент получения данных.
Рассмотрим подробнее пример кода:
// НЕПРАВИЛЬНО:
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Остатки
|ГДЕ
| Остатки.Склад = &НужныйСклад";
// ПРАВИЛЬНО:
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &НужныйСклад) КАК Остатки";
Во втором варианте время выполнения запроса может сократиться в десятки раз, так как сервер БД будет обрабатывать только данные по конкретному складу, а не по всей компании сразу.
Еще один важный аспект, который мы разберем — это обращение к полям через точку в тексте запроса (например, Продажи.Номенклатура.Артикул). Каждый раз, когда вы используете точку от ссылочного поля, 1С неявно добавляет ЛЕВОЕ СОЕДИНЕНИЕ со справочником в итоговый SQL-запрос. Если таких обращений много, количество соединений в запросе растет лавинообразно.
Выясним причину замедления на примере. Допустим, вам нужно получить Артикул и Группу номенклатуры. Вместо Продажи.Номенклатура.Артикул и Продажи.Номенклатура.Родитель, гораздо эффективнее один раз соединиться с таблицей справочника Номенклатура.
Давайте посмотрим на пример оптимизированного кода:
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Количество КАК Кол,
| СпрНоменклатура.Артикул КАК Артикул,
| СпрНоменклатура.Наименование КАК Наименование
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Продажи
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
| ПО Продажи.Номенклатура = СпрНоменклатура.Ссылка
|ГДЕ
| Продажи.Ссылка = &СсылкаНаДокумент";
В данном случае мы явно контролируем процесс соединения. Это особенно важно, если вам нужно получить 5-10 реквизитов одного и того же объекта.
Чтобы система работала быстро, необходимо понимать, какие поля должны быть проиндексированы. В 1С автоматически индексируются все стандартные поля (Ссылка, Дата, Код, Номер). Однако для дополнительных реквизитов, по которым часто происходит поиск или фильтрация, индекс нужно включать вручную в конфигураторе (свойство Индексировать).
Проанализируем рекомендации по индексации:
ГДЕ.ПО Таблица1.Поле = Таблица2.Поле).В высоконагруженных системах оптимизация запроса — это не только скорость, но и отсутствие блокировок. Если запрос выполняется долго, он может заблокировать таблицу, и другие пользователи не смогут проводить документы. Для уменьшения вероятности блокировок используйте:
*, указывайте только нужные поля).Подведем итог нашего анализа. Оптимизация 1С — это комплексный процесс. Мы рассмотрели базовые, но самые эффективные техники: использование МенеджерВременныхТаблиц с индексами, правильную передачу параметров в виртуальные таблицы и контроль соединений. Применяя эти правила на практике, вы сможете добиться стабильной и быстрой работы системы даже при значительном росте базы данных.