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