Как оптимизировать запрос в 1С при помощи временных таблиц и повысить производительность системы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда запрос, идеально работающий на тестовой базе, начинает катастрофически замедляться на реальных данных. Одной из главных причин такого поведения является использование сложных вложенных запросов в условии ГДЕ или соединений с виртуальными таблицами. Давайте проанализируем, почему это происходит, и разберем по шагам, как перевести такие конструкции на использование временных таблиц.

Выясним причину: почему вложенные запросы замедляют работу?

Когда мы используем конструкцию типа ГДЕ Ссылка В (ВЫБРАТЬ...), оптимизатор системы управления базами данных (СУБД), будь то MS SQL Server или PostgreSQL, не всегда может эффективно определить объем данных, который вернет вложенный запрос. В результате выбирается неоптимальный план выполнения, что приводит к полному сканированию таблиц (Index Scan или Table Scan) вместо быстрого поиска по индексу. Чтобы избежать этого, мы воспользуемся механизмом, который предоставляет нам МенеджерВременныхТаблиц.

Шаг 1: Подготовка Менеджера временных таблиц

Прежде чем приступать к написанию самого текста запроса, нам необходимо инициализировать объект, который будет хранить наши промежуточные данные в оперативной памяти или в специальных системных таблицах СУБД (tempdb). Рассмотрим пример инициализации в коде 1С:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

Использование МенеджерВременныхТаблиц позволяет нам связывать несколько последовательных запросов в одну логическую цепочку, где результаты предыдущего шага доступны для последующих. Это критически важно для структурирования сложной логики.

Шаг 2: Вынос вложенного запроса в отдельную временную таблицу

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

Проанализируем текст первого запроса, который подготавливает данные:


Запрос.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ
|   ПродажиОбороты.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
|   РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК ПродажиОбороты
|ИНДЕКСИРОВАТЬ ПО
|   Номенклатура";
Запрос.Выполнить();

Обратите внимание на ключевое слово ПОМЕСТИТЬ. Оно указывает платформе 1С, что результат выполнения не нужно возвращать в код, а следует сохранить в таблицу с именем ВТ_ПроданныеТовары. Также крайне важным моментом является использование предложения ИНДЕКСИРОВАТЬ ПО. Индексация временной таблицы позволяет СУБД мгновенно находить соответствия при последующем соединении с основной таблицей остатков.

Шаг 3: Основной запрос и использование индексированных данных

Теперь, когда наши данные подготовлены и проиндексированы, мы можем выполнить основной запрос. Посмотрим на пример кода, где мы соединяем остатки с нашей временной таблицей:


Запрос.Текст = 
"ВЫБРАТЬ
|   ТоварыНаСкладахОстатки.Номенклатура,
|   ТоварыНаСкладахОстатки.КоличествоОстаток
|ИЗ
|   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПроданныеТовары КАК ВТ_ПроданныеТовары
|ПО ТоварыНаСкладахОстатки.Номенклатура = ВТ_ПроданныеТовары.Номенклатура";

РезультатЗапроса = Запрос.Выполнить();

В данном случае ВНУТРЕННЕЕ СОЕДИНЕНИЕ с предварительно отобранными данными работает значительно быстрее, чем использование условий во вложенных подзапросах. Мы фактически ограничили выборку остатков только теми позициями, которые гарантированно есть в нашей ВТ_ПроданныеТовары.

Разберем важные нюансы работы с временными таблицами

Рассмотрим несколько правил, которые помогут вам писать эффективный код:

  1. Объем данных: Не стоит помещать во временные таблицы миллионы строк. Если данных слишком много, создание самой таблицы и ее индексация могут занять больше времени, чем выполнение прямого запроса. Оптимально использовать временные таблицы для выборок от нескольких сотен до нескольких десятков тысяч строк.
  2. Индексация: Всегда индексируйте те поля временной таблицы, по которым в дальнейшем будет происходить СОЕДИНЕНИЕ или фильтрация в секции ГДЕ. Без индекса временная таблица — это просто неструктурированный набор данных.
  3. Типизация полей: При создании временной таблицы через конструкцию ВЫБРАТЬ... ПОМЕСТИТЬ, старайтесь избегать полей с составным типом данных. Это может негативно сказаться на скорости индексации.
  4. Очистка ресурсов: После того как временная таблица больше не нужна, хорошим тоном считается её удаление. Это освобождает ресурсы СУБД.

Посмотрим, как правильно очистить менеджер временных таблиц:


Запрос.Текст = "УНИЧТОЖИТЬ ВТ_ПроданныеТовары";
Запрос.Выполнить();

Сравним подходы: Вложенный запрос vs Временная таблица

Проанализируем ситуацию на конкретном примере. Допустим, нам нужно получить данные из справочника Номенклатура, исключая те элементы, которые помечены на удаление или входят в определенный сегмент.

Плохо (Вложенный запрос):


ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура
ГДЕ Ссылка В (ВЫБРАТЬ Номенклатура ИЗ РегистрСведений.НоменклатураСегмента ГДЕ Сегмент = &Сегмент)

Хорошо (Временная таблица):

  1. Сначала выбираем данные сегмента в ВТ_Сегмент.
  2. Индексируем ВТ_Сегмент по полю Номенклатура.
  3. Соединяем Справочник.Номенклатура с ВТ_Сегмент через ВНУТРЕННЕЕ СОЕДИНЕНИЕ.

Такой подход гарантирует, что СУБД построит план запроса на основе Nested Loops или Hash Match, используя существующие индексы основной таблицы и созданный нами индекс временной таблицы. Это исключает непредсказуемое поведение оптимизатора.

Когда временные таблицы необходимы?

Разберем ситуации, в которых без этого механизма практически не обойтись:

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

← На главную