Как оптимизировать медленно работающий запрос в 1С:Предприятии и ускорить его выполнение?

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

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

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

1. Эффективное использование индексов: основа производительности

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

  1. Автоматические и явные индексы:

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

    Помимо автоматических, мы можем явно управлять индексированием реквизитов и измерений, устанавливая свойство Индексировать в конфигураторе. Это особенно важно для полей, по которым мы часто выполняем отборы, сортировки или используем их в условиях соединений. Если мы видим, что запрос тормозит по какому-либо полю, которое активно используется в ГДЕ или СОЕДИНЕНИИ, но не является измерением или ключевым реквизитом, нам следует рассмотреть возможность его индексации.

    Например, в нашем случае, если запрос медленно работает с полями ИерархияУпаковки и Упаковка, мы должны обязательно проверить наличие индексов по этим полям. Без индекса по Упаковка запрос, скорее всего, не сможет работать эффективно.

  2. Индексирование временных таблиц:

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

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

  3. Переиндексация и обновление статистики:

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

2. Условия в параметрах виртуальных таблиц против условий в предложении ГДЕ: критическое различие

Это один из наиболее важных аспектов оптимизации запросов, особенно при работе с виртуальными таблицами, такими как СрезПоследних или Остатки. Способ указания отбора может кардинально повлиять как на производительность, так и на логику получения результата.

  1. Условие в параметрах виртуальной таблицы:

    Когда мы указываем условие непосредственно в параметрах виртуальной таблицы (например, РегистрСведений.ИмяРегистра.СрезПоследних(, Измерение = &Значение)), СУБД применяет этот фильтр на этапе построения самой виртуальной таблицы. Это означает, что она сразу отсекает ненужные данные, существенно сокращая объем информации, с которой ей предстоит работать. Такой подход является предпочтительным с точки зрения производительности, так как минимизирует объем обрабатываемых данных.

    Посмотрим на пример из форума:

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

    В этом запросе условие Упаковка = &Штрихкод (или ИерархияУпаковки = &Штрихкод) передается непосредственно в параметры функции СрезПоследних, что является правильным подходом для оптимизации. Мы сокращаем количество данных, из которых строится срез, с самого начала.

  2. Условие в предложении ГДЕ:

    Когда мы указываем условие в предложении ГДЕ (например, ИЗ РегистрСведений.ИмяРегистра.СрезПоследних() КАК СрезПоследних ГДЕ СрезПоследних.Измерение = &Значение), это условие применяется после того, как виртуальная таблица уже полностью построена. В этом случае СУБД сначала тратит ресурсы на построение всего среза (или остатков) по всем измерениям, а затем отфильтровывает из него записи, не удовлетворяющие условию. Это приводит к избыточной работе и значительному снижению производительности.

    Рассмотрим пример из форума, где условие ГДЕ применяется к уже построенному срезу:

    
    ВЫБРАТЬ
        алкХранилищеУпаковокСрезПоследних.Упаковка КАК Упаковка
    ИЗ
        РегистрСведений.алкХранилищеУпаковок.СрезПоследних(, ) КАК алкХранилищеУпаковокСрезПоследних
    ГДЕ
        алкХранилищеУпаковокСрезПоследних.Упаковка = &Штрихкод
    

    Такой подход может быть крайне неэффективным, если регистр содержит много данных, а условие в ГДЕ сильно сокращает выборку. Мы строим большой срез, а потом почти все отбрасываем.

  3. Важное различие в логике для СрезПоследних:

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

    
    // Исходные данные регистра сведений:
    // Родитель | Состояние | Период
    // ----------|-----------|------------
    // Папа      | Бухой     | 01.01.2025
    // Папа      | Трезвый   | 01.03.2025
    // Папа      | Бухой     | 01.05.2025
    // Папа      | Трезвый   | 24.09.2025
    

    a) Запрос с условием в параметрах СрезПоследних по измерению:

    
    ВЫБРАТЬ
        СрезПоследних.Состояние
    ИЗ
        РегистрСведений.МойРегистр.СрезПоследних(, Родитель = &Папа) КАК СрезПоследних
    ГДЕ
        СрезПоследних.Состояние = &Бухой
    

    В этом случае СрезПоследних сначала найдет последнюю запись по измерению "Папа" (это будет строка "24.09.2025 Папа Трезвый"). Затем к этой единственной записи будет применено условие ГДЕ СрезПоследних.Состояние = &Бухой. Результат: <Нет строк>, так как последняя запись "Трезвый", а не "Бухой".

    b) Запрос с условием в параметрах СрезПоследних по ресурсу/реквизиту:

    
    ВЫБРАТЬ
        СрезПоследних.Состояние,
        СрезПоследних.Период
    ИЗ
        РегистрСведений.МойРегистр.СрезПоследних(, Состояние = &Бухой) КАК СрезПоследних
    

    Здесь СрезПоследних постарается найти последнюю запись, где Состояние было "Бухой". Результат: "01.05.2025 Папа Бухой".

    Это показывает, что отбор по ресурсу или реквизиту в параметрах СрезПоследних может привести к тому, что будет выбрана *последняя* запись по измерениям, даже если значение этого ресурса/реквизита не соответствует условию. В то время как условие в ГДЕ отфильтрует такие записи из уже построенного среза. Мы должны четко понимать эту логику при построении запросов.

3. Проектирование регистров сведений: фундамент производительности

"Кривое" проектирование регистров сведений часто является первопричиной медленной работы запросов. Если регистр спроектирован неоптимально, даже самые изощренные методы оптимизации запросов могут не дать желаемого результата.

  1. Анализ структуры и периодичности:

    При создании периодических регистров мы должны тщательно анализировать, как данные изменяются во времени. Каждая запись в периодическом регистре должна отражать факт установки значений *всех* ресурсов на определенный момент времени. Если это не так, и записи в регистре не являются "срезами" состояния, то использование виртуальных таблиц СрезПоследних или СрезПервых может быть некорректным или неэффективным.

  2. Избегайте избыточных измерений:

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

  3. Новые режимы записи:

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

4. Структура запросов: ОБЪЕДИНИТЬ ВСЕ и временные таблицы

Для сложных запросов, включающих несколько условий или источников данных, мы можем использовать операторы ОБЪЕДИНИТЬ ВСЕ и временные таблицы для повышения производительности.

  1. ОБЪЕДИНИТЬ ВСЕ:

    В некоторых случаях замена оператора ИЛИ на ОБЪЕДИНИТЬ ВСЕ может ускорить выполнение запроса, особенно если условия ИЛИ применяются к проиндексированным полям. Оператор ОБЪЕДИНИТЬ ВСЕ отличается от ОБЪЕДИНИТЬ тем, что он не выполняет группировку и удаление дублирующихся строк, что может быть значительно быстрее при больших объемах данных, если нам не требуется уникальность строк в результате.

    Посмотрим на пример из форума, где используется ОБЪЕДИНИТЬ ВСЕ:

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

    Здесь мы объединяем результаты трех отдельных выборок, что может быть быстрее, чем пытаться реализовать все условия в одном блоке ГДЕ с оператором ИЛИ, особенно если источники данных разные или требуют разных виртуальных таблиц.

  2. Временные таблицы:

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

    Рекомендации по работе с временными таблицами:

    • Мы должны выносить обращения к медленным виртуальным таблицам (например, к СрезПоследних или Остатки) в отдельные запросы с сохранением результатов во временные таблицы. Это позволяет СУБД один раз построить сложный набор данных, а затем многократно использовать его в других частях запроса.
    • Важно максимально ограничивать объем данных, выбираемых во временные таблицы. Мы не должны помещать в них неиспользуемые поля, чтобы не увеличивать нагрузку на СУБД.
    • Мы должны избегать создания и удаления временных таблиц в цикле, так как это приводит к значительным накладным расходам. Если нам нужно многократно работать с одними и теми же данными, лучше создать временную таблицу один раз и использовать ее.
    • Например, если нам нужно отобрать упаковки, а затем с этим фильтром работать дальше, мы можем сначала поместить отобранные упаковки во временную таблицу.

5. Составные типы данных: скрытые ловушки производительности

Использование составных типов данных, особенно в условиях запросов и при обращении к полям через точку ("разыменование ссылочного поля"), может значительно снижать производительность. Мы должны быть особенно внимательны к типу значения, по которому ставим фильтр, чтобы он не был составным без необходимости.

  1. Проблема составных типов:

    При обращении к полю составного типа через точку (например, Ссылка.Реквизит) система неявно создает дополнительные запросы и соединяет таблицы всех возможных типов, входящих в составной тип. Это усложняет SQL-текст, может приводить к неоптимальным планам выполнения запросов и замедлять их.

    Индексирование поля составного типа данных также увеличивает нагрузку, поскольку СУБД приходится создавать индексы для каждого отдельного типа, входящего в составной.

  2. Рекомендации по работе с составными типами:

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

6. Прямые запросы к БД (SQL): крайняя мера

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

Прямые запросы требуют глубокого понимания работы СУБД, структуры таблиц 1С и платформы в целом. Их использование может усложнить поддержку системы и привести к проблемам при обновлении конфигурации. Мы всегда должны сначала исчерпать все возможности оптимизации на уровне языка запросов 1С, прежде чем прибегать к прямым SQL-запросам.

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

← К списку