Почему тормозит простой запрос по табличным частям документов и как его оптимизировать

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

В практике разработчика 1С часто встречается ситуация, когда внешне простой запрос, выбирающий данные из пары табличных частей документа, начинает выполняться недопустимо долго. В нашем примере запрос по документам ЧекККМ в конфигурации УТ 11.4 возвращал всего 227 записей, но время выполнения составляло более 50 секунд. Разберем детально, почему так происходит и как превратить медленный запрос в эффективный механизм.

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

Рассмотрим исходный запрос, который стал причиной "тормозов":


ВЫБРАТЬ
    ЧекККМТовары.Номенклатура КАК Номенклатура,
    ЧекККМТовары.СуммаАвтоматическойСкидки КАК Скидка,
    ЧекККМСкидкиНаценки.СкидкаНаценка КАК СкидкаНаценка,
    ЧекККМТовары.Продавец КАК Продавец,
    ЧекККМТовары.Ссылка.Склад КАК Склад
ИЗ
    Документ.ЧекККМ.Товары КАК ЧекККМТовары
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.СкидкиНаценки КАК ЧекККМСкидкиНаценки
        ПО ЧекККМТовары.Ссылка = ЧекККМСкидкиНаценки.Ссылка
            И ЧекККМТовары.КлючСвязи = ЧекККМСкидкиНаценки.КлючСвязи
ГДЕ
    ЧекККМТовары.Ссылка.Дата >= &ДатаНачала
    И ЧекККМТовары.Ссылка.Дата <= &ДатаОкончания

Проанализируем ситуацию. На первый взгляд запрос кажется логичным, но он содержит критические архитектурные недостатки для платформы 1С и SQL-сервера:

  1. Обращение через точку (Разыменование ссылок): Использование конструкций ЧекККМТовары.Ссылка.Дата и ЧекККМТовары.Ссылка.Склад заставляет систему выполнять неявные соединения с основной таблицей документа. Если в запросе много таких обращений, SQL-сервер строит сложный план с множеством лишних JOIN.
  2. Отсутствие фильтрации во второй таблице: При внутреннем соединении SQL-серверу приходится сопоставлять огромные массивы данных из обеих табличных частей (Товары и СкидкиНаценки) еще до того, как сработает условие ГДЕ.
  3. Механизм RLS: В типовых конфигурациях (УТ, ERP, КА) на документы ЧекККМ часто наложены ограничения доступа. При обращении к полям через Ссылка система добавляет к вашему коду массивные подзапросы для проверки прав, что замедляет работу в геометрической прогрессии.

Шаг 1: Использование временных таблиц для фильтрации

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

Создадим временную таблицу ВТ_ЧекККМ, где выберем только нужные нам ссылки и необходимые реквизиты шапки (например, Склад), чтобы потом не обращаться к ним через точку.


ВЫБРАТЬ
    ЧекККМ.Ссылка КАК Ссылка,
    ЧекККМ.Склад КАК Склад
ПОМЕСТИТЬ ВТ_ЧекККМ
ИЗ
    Документ.ЧекККМ КАК ЧекККМ
ГДЕ
    ЧекККМ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

Почему это работает? Мы получили компактный список документов. Поле Дата в основной таблице документа проиндексировано, поэтому отбор по периоду отработает мгновенно. Использование ИНДЕКСИРОВАТЬ ПО позволит SQL-серверу быстрее выполнять последующие соединения.

Шаг 2: Правильное соединение табличных частей

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

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

Разберем пример оптимизированного соединения:


ВЫБРАТЬ
    ЧекККМТовары.Номенклатура КАК Номенклатура,
    ЧекККМТовары.СуммаАвтоматическойСкидки КАК Скидка,
    ЧекККМСкидкиНаценки.СкидкаНаценка КАК СкидкаНаценка,
    ЧекККМТовары.Продавец КАК Продавец,
    ВТ_ЧекККМ.Склад КАК Склад
ИЗ
    ВТ_ЧекККМ КАК ВТ_ЧекККМ
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.Товары КАК ЧекККМТовары
        ПО ВТ_ЧекККМ.Ссылка = ЧекККМТовары.Ссылка
        
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.СкидкиНаценки КАК ЧекККМСкидкиНаценки
        ПО ВТ_ЧекККМ.Ссылка = ЧекККМСкидкиНаценки.Ссылка
            И ЧекККМТовары.КлючСвязи = ЧекККМСкидкиНаценки.КлючСвязи

Шаг 3: Тонкая настройка — индексация и условия связей

Проанализируем еще один важный момент: выбор между ЛЕВОЕ СОЕДИНЕНИЕ и ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Если мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ, то в результат попадут только те товары, по которым есть записи в таблице скидок. Если же нам нужны все проданные товары (даже без скидок), нужно использовать ЛЕВОЕ СОЕДИНЕНИЕ.

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

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

  1. Помещаем товары отобранных чеков в ВТ_Товары и индексируем по Ссылка и КлючСвязи.
  2. Помещаем скидки тех же чеков в ВТ_Скидки и также индексируем.
  3. Соединяем две временные таблицы.

Это может показаться избыточным, но на объемах в десятки тысяч строк такой подход работает значительно стабильнее, так как исключает "сканирование" (Full Scan) больших таблиц.

Итоговое решение (код)

Сведем все рекомендации в один эффективный запрос, который будет работать быстро даже в крупных базах УТ 11.4:


ВЫБРАТЬ
    ЧекККМ.Ссылка КАК Ссылка,
    ЧекККМ.Склад КАК Склад
ПОМЕСТИТЬ ВТ_СписокЧеков
ИЗ
    Документ.ЧекККМ КАК ЧекККМ
ГДЕ
    ЧекККМ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Товары.Ссылка КАК Ссылка,
    Товары.Номенклатура КАК Номенклатура,
    Товары.Продавец КАК Продавец,
    Товары.СуммаАвтоматическойСкидки КАК Скидка,
    Товары.КлючСвязи КАК КлючСвязи
ПОМЕСТИТЬ ВТ_ТоварыЧеков
ИЗ
    Документ.ЧекККМ.Товары КАК Товары
ГДЕ
    Товары.Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ_СписокЧеков)
ИНДЕКСИРОВАТЬ ПО
    Ссылка,
    КлючСвязи
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Скидки.Ссылка КАК Ссылка,
    Скидки.КлючСвязи КАК КлючСвязи,
    Скидки.СкидкаНаценка КАК СкидкаНаценка
ПОМЕСТИТЬ ВТ_СкидкиЧеков
ИЗ
    Документ.ЧекККМ.СкидкиНаценки КАК Скидки
ГДЕ
    Скидки.Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ_СписокЧеков)
ИНДЕКСИРОВАТЬ ПО
    Ссылка,
    КлючСвязи
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ТоварыЧеков.Номенклатура КАК Номенклатура,
    ВТ_ТоварыЧеков.Скидка КАК Скидка,
    ВТ_СкидкиЧеков.СкидкаНаценка КАК СкидкаНаценка,
    ВТ_ТоварыЧеков.Продавец КАК Продавец,
    ВТ_СписокЧеков.Склад КАК Склад
ИЗ
    ВТ_СписокЧеков КАК ВТ_СписокЧеков
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ТоварыЧеков КАК ВТ_ТоварыЧеков
        ПО ВТ_СписокЧеков.Ссылка = ВТ_ТоварыЧеков.Ссылка
        
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СкидкиЧеков КАК ВТ_СкидкиЧеков
        ПО ВТ_ТоварыЧеков.Ссылка = ВТ_СкидкиЧеков.Ссылка
            И ВТ_ТоварыЧеков.КлючСвязи = ВТ_СкидкиЧеков.КлючСвязи

Подведем итоги

Проанализировав ситуацию, мы выяснили, что "простые" запросы в 1С часто скрывают под собой тяжелые операции для СУБД. Основными инструментами оптимизации для нас стали:

Применение этих правил позволяет сократить время выполнения запроса с 50-60 секунд до долей секунды, обеспечивая комфортную работу пользователей даже в высоконагруженных розничных системах.

← На главную