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

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

Разработчики в среде 1С часто сталкиваются со снижением производительности систем при выполнении операций динамического подбора товаров. Типичный сценарий — интерактивный поиск по строке, при котором система должна быстро выдать список номенклатуры, актуальные остатки на складах и действующие розничные цены. Если база данных содержит десятки тысяч позиций, а запросы написаны неоптимально, время ожидания ответа пользователем может составлять несколько секунд, что абсолютно неприемлемо для комфортной работы.

В этой статье мы подробно разберем, почему исходный запрос автора темы выполнялся крайне медленно (около 3–4 секунд), детально проанализируем каждую ошибку в структуре запроса и пошагово оптимизируем его с использованием временных таблиц, индексирования и правильной передачи параметров СУБД.

---

Анализ исходной проблемы и разбор ошибок

Давайте посмотрим на первоначальный вариант запроса, который вызывал серьезные задержки в работе системы:


ВЫБРАТЬ ПЕРВЫЕ 200
    Номен.Ссылка КАК Номенклатура,
    ТоварыНаСкладахОстатки.Характеристика КАК Серия,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток,
    ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
ИЗ
    Справочник.Номенклатура КАК Номен
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &РозничныйСклад) КАК ТоварыНаСкладахОстатки
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ВидЦены = &Розничная) КАК ЦеныНоменклатурыСрезПоследних
        ПО ТоварыНаСкладахОстатки.Характеристика = ЦеныНоменклатурыСрезПоследних.Характеристика
    ПО Номен.Ссылка = ТоварыНаСкладахОстатки.Характеристика.Владелец
ГДЕ
    Номен.НАИМЕНОВАНИЕ ПОДОБНО "%" + &СтрокаПоиска + "%"
УПОРЯДОЧИТЬ ПО
    Остаток УБЫВ

Проанализируем данную конструкцию и выясним, почему СУБД (будь то Microsoft SQL Server или PostgreSQL) тратит так много времени на её выполнение. Выделим четыре ключевые проблемы:

  1. Сканирование всей таблицы из-за оператора ПОДОБНО с лидирующим процентом. Использование конструкции ПОДОБНО "%" + &СтрокаПоиска + "%" заставляет СУБД выполнять операцию полного сканирования таблицы (Table Scan или Index Scan) вместо быстрого поиска по индексу (Index Seek). Так как символ % стоит в самом начале шаблона поиска, СУБД не может применить стандартный индекс по наименованию номенклатуры.
  2. Отсутствие фильтрации в виртуальных таблицах. Виртуальные таблицы остатков регистра накопления ТоварыНаСкладах.Остатки и среза последних регистра сведений ЦеныНоменклатуры.СрезПоследних не имеют ограничений по номенклатуре в своих параметрах. Это означает, что при каждом запуске запроса платформа 1С пытается рассчитать остатки и получить цены абсолютно по всей номенклатуре и всем характеристикам, которые есть в базе данных. И только потом, на самом последнем этапе, происходит соединение и отсечение по условию ГДЕ.
  3. Неявные соединения (разыменование полей через точку). В условии связи ПО Номен.Ссылка = ТоварыНаСкладахОстатки.Характеристика.Владелец используется обращение через точку к реквизиту Владелец ссылочного типа Характеристика. На уровне СУБД это заставляет платформу генерировать дополнительное неявное левое соединение с таблицей справочника характеристик. Подобные неявные связи сильно запутывают оптимизатор СУБД и приводят к неоптимальным планам выполнения запроса.
  4. Некорректная структура вложенных соединений. Соединение таблицы цен происходит по полю Характеристика с таблицей остатков. Если у товара нет остатка на складе (запись в ТоварыНаСкладахОстатки отсутствует), то связующее поле превратится в NULL, и цена для этого товара также не будет получена, даже если она установлена в системе.

---

Пошаговая реструктуризация и оптимизация запроса

Для того чтобы кардинально ускорить выборку, нам необходимо применить классический паттерн разработки в 1С: разбиение сложного запроса на пакет с использованием временных таблиц (ВТ). Это позволит СУБД обрабатывать промежуточные данные небольшими порциями, используя эффективные индексы.

Шаг 1. Отбор целевой номенклатуры во временную таблицу

Сначала мы выберем только ту номенклатуру, которая соответствует строке поиска. Это позволит нам сузить объем обрабатываемых данных на последующих шагах до минимума.


ВЫБРАТЬ
    Номен.Ссылка КАК Номенклатура
ПОМЕСТИТЬ ВрТовары
ИЗ
    Справочник.Номенклатура КАК Номен
ГДЕ
    Номен.Наименование ПОДОБНО &ШаблонПоиска

ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

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

Шаг 2. Получение остатков только по отобранной номенклатуре

Теперь мы обратимся к виртуальной таблице остатков. Но вместо того чтобы рассчитывать остатки по всему складу, мы передадим нашу временную таблицу ВрТовары прямо в параметры виртуальной таблицы в качестве фильтра.


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

ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    Характеристика
;

Благодаря конструкции Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВрТовары), СУБД рассчитает остатки только по тем товарам, которые подходят под наше поисковое условие. Это сокращает объем обрабатываемых строк в сотни и тысячи раз.

Шаг 3. Получение цен только по отобранной номенклатуре

Аналогичным образом поступим и с получением цен. Передадим фильтр по номенклатуре в параметры виртуальной таблицы среза последних:


ВЫБРАТЬ
    Цены.Номенклатура КАК Номенклатура,
    Цены.Характеристика КАК Характеристика,
    Цены.Цена КАК Цена
ПОМЕСТИТЬ ВрЦены
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ВидЦены = &Розничная И Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВрТовары)) КАК Цены

ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    Характеристика
;

Шаг 4. Финальное объединение данных

На последнем этапе мы соединяем подготовленные и проиндексированные временные таблицы. Обратите внимание на структуру соединений. Чтобы избежать проблемы потери цен у товаров с нулевыми остатками, мы будем соединять таблицы остатков и цен напрямую к базовой временной таблице ВрТовары.


ВЫБРАТЬ ПЕРВЫЕ 200
    ВрТовары.Номенклатура КАК Номенклатура,
    ЕСТЬNULL(ВрОстатки.Характеристика, ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)) КАК Характеристика,
    ЕСТЬNULL(ВрОстатки.КоличествоОстаток, 0) КАК Остаток,
    ЕСТЬNULL(ВрЦены.Цена, 0) КАК Цена
ИЗ
    ВрТовары КАК ВрТовары
    ЛЕВОЕ СОЕДИНЕНИЕ ВрОстатки КАК ВрОстатки
        ПО ВрТовары.Номенклатура = ВрОстатки.Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ ВрЦены КАК ВрЦены
        ПО ВрТовары.Номенклатура = ВрЦены.Номенклатура
        И (ВрОстатки.Характеристика = ВрЦены.Характеристика ИЛИ ВрОстатки.Характеристика ЕСТЬ NULL)
УПОРЯДОЧИТЬ ПО
    Остаток УБЫВ

---

Критически важные архитектурные нюансы оптимизации

Рассмотрим несколько продвинутых аспектов оптимизации подобных запросов, которые напрямую влияют на стабильность работы высоконагруженных систем.

1. Логическая ловушка с «ВЫБРАТЬ ПЕРВЫЕ 200»

В процессе обсуждения на форумах часто предлагают поместить ограничение ВЫБРАТЬ ПЕРВЫЕ 200 в самый первый запрос пакета (при формировании ВрТовары). **Делать так категорически не рекомендуется по соображениям бизнес-логики!**

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

2. Проблема оператора «ПОДОБНО» и альтернатива в виде Полнотекстового Поиска (ПСП)

Даже оптимизированный через временные таблицы запрос может работать медленно, если справочник номенклатуры содержит сотни тысяч записей, а поиск выполняется по подстроке в середине слова. Конкатенация строк вида "%" + &Строка + "%" на уровне СУБД всегда порождает тяжелые операции сканирования.

Чтобы решить эту проблему кардинально, рассмотрим переход на встроенный механизм полнотекстового поиска 1С. Реализовать это можно следующим образом на встроенном языке:


СписокСсылок = Новый Массив;
СписокПоиска = ПолнотекстовыйПоиск.СоздатьСписок(СтрокаПоиска);
СписокПоиска.ПолучитьПорцию();

Для Каждого СтрокаРезультата Из СписокПоиска Цикл
    Если ТипЗнч(СтрокаРезультата.Значение) = Тип("СправочникСсылка.Номенклатура") Тогда
        СписокСсылок.Добавить(СтрокаРезультата.Значение);
    КонецЕсли;
КонецЦикла;

После получения массива подходящих ссылок СписокСсылок через полнотекстовый индекс (который работает практически мгновенно), мы можем передать этот массив в параметры нашего запроса:


ГДЕ
    Номен.Ссылка В (&СписокСсылок)

Такой подход полностью исключает использование медленного оператора ПОДОБНО и переводит производительность поиска на совершенно иной уровень.

3. Использование итогов среза последних в регистре сведений

Для виртуальной таблицы СрезПоследних регистра цен крайне важно включить свойство «Разрешить итоги: Срез последних» в конфигураторе 1С.

По умолчанию СУБД вынуждена рассчитывать срез последних динамически, выполняя ресурсоемкие подзапросы с группировками по максимальной дате. Включение итогов среза последних заставляет платформу автоматически поддерживать в базе данных скрытую реальную таблицу со значениями актуальных цен. Это превращает тяжелый расчет среза последних в легкое и быстрое чтение из плоской таблицы.

---

Резюме результатов оптимизации

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

Применение рассмотренных методик позволит вашим интерфейсам подбора работать мгновенно даже на базах данных терабайтного объема с высокой плотностью транзакций.

← На главную