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

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

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

Почему запросы в цикле замедляют систему?

Когда мы выполняем запрос внутри цикла, программа вынуждена на каждой итерации обращаться к серверу приложений, а тот, в свою очередь, к серверу СУБД (SQL Server, PostgreSQL и др.). Рассмотрим ситуацию: если в цикле 1000 итераций, система выполнит 1000 отдельных сетевых вызовов и 1000 операций компиляции и исполнения SQL-кода. Это создает колоссальную нагрузку на сетевую инфраструктуру и ресурсы сервера.

Проанализируем основные факторы замедления:

  1. Сетевые задержки (Latency): Каждое обращение — это время на передачу пакета данных туда и обратно.
  2. Накладные расходы СУБД: СУБД должна каждый раз строить план выполнения запроса и проверять права доступа.
  3. Блокировки: Многократные короткие обращения могут создавать избыточные ожидания на уровне блокировок таблиц базы данных.

Метод решения: Использование временных таблиц

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

Сначала подготовим данные во внешней таблице значений и передадим её в запрос в качестве параметра. Это позволит нам один раз обратиться к базе данных и получить результат для всех строк сразу.

Рассмотрим пример кода, где мы передаем таблицу СписокТоваров в запрос:


// Создаем таблицу значений с исходными данными
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Склад", Новый ОписаниеТипов("СправочникСсылка.Склады"));

// Заполняем таблицу (предположим, она уже заполнена из формы или другого источника)
// ...

Запрос = Новый Запрос;
// Устанавливаем таблицу как параметр
Запрос.УстановитьПараметр("ВнешняяТаблица", ТаблицаДанных);

Работа с Менеджером временных таблиц

Для эффективной обработки данных в несколько этапов нам потребуется объект МенеджерВременныхТаблиц. Он позволяет сохранять промежуточные результаты запроса в памяти СУБД (или во временных файлах) и обращаться к ним повторно в рамках одной сессии работы с запросом.

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

Разберем пример реализации пакетного запроса:


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

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

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

Важность индексации временных таблиц

Если во временной таблице содержится значительное количество записей (более 50-100), ее обязательно нужно индексировать. Без индекса соединение временной таблицы с основными таблицами базы данных будет выполняться крайне медленно (используя алгоритм Nested Loops или Hash Match без оптимизации по ключу).

Посмотрим на пример правильной индексации в тексте запроса:


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

Алгоритм перехода от запроса в цикле к пакетному запросу

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

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

Типичные ошибки при работе с временными таблицами

Рассмотрим подробнее ситуации, которых следует избегать:

Соблюдение этих правил позволяет ускорить выполнение операций в десятки, а иногда и в сотни раз, особенно в высоконагруженных конфигурациях типа 1С:ERP или 1С:Управление торговлей.

← На главную