Как оптимизировать медленный запрос к регистру сведений с составными типами?

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

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

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


ВЫБРАТЬ
    Активность.Значение КАК Дата,
    Активность.Объект.Контрагент КАК Контрагент
ИЗ
    РегистрСведений.Активность КАК Активность
ГДЕ
    Активность.Реквизит = "Срок"
    И ВЫРАЗИТЬ(Активность.Значение КАК СТРОКА(100)) ПОДОБНО &ДатаЗавтра + "%"
    И Активность.Объект.Обещание.Код = "000000077"

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

Оптимизация обращений к полям составного типа

Одной из главных причин замедления является обращение через «точку» к полям Активность.Объект.Контрагент и Активность.Объект.Обещание. Если поле Объект имеет составной тип (например, может содержать ссылку на несколько разных документов или справочников), платформа 1С генерирует SQL-запрос с использованием LEFT JOIN ко всем таблицам, входящим в этот тип.

Выясним причину: если в составной тип входит 10 справочников, СУБД будет вынуждена соединить основную таблицу с 10-ю другими, чтобы найти нужное поле. Если же «точки» продолжаются дальше (как в Активность.Объект.Обещание.Код), количество соединений растет в геометрической прогрессии.

Чтобы избежать этой проблемы, применим функцию ВЫРАЗИТЬ. Это позволит платформе четко понимать, к какой именно таблице нужно обращаться, и исключит лишние соединения. Рассмотрим, как изменить этот фрагмент:


// Вместо Активность.Объект.Контрагент используем:
ВЫРАЗИТЬ(Активность.Объект КАК Справочник.Задачи).Контрагент

// Вместо Активность.Объект.Обещание.Код используем:
ВЫРАЗИТЬ(ВЫРАЗИТЬ(Активность.Объект КАК Справочник.Задачи).Обещание КАК Справочник.Обещания).Код

Важно: Всегда старайтесь ограничивать количество уровней вложенности («точек»). Каждая «точка» — это потенциальное замедление. Если возможно, лучше заранее получить нужную ссылку и передать ее в параметры запроса.

Правильная работа с условиями поиска (SARGability)

Проанализируем условие ВЫРАЗИТЬ(Активность.Значение КАК СТРОКА(100)) ПОДОБНО &ДатаЗавтра + "%". Это классический пример того, как нельзя писать условия в 1С. Когда мы применяем любую функцию (будь то ВЫРАЗИТЬ, ГОД(), ПОДСТРОКА()) к индексированному полю, СУБД теряет возможность использовать индекс по этому полю.

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

Решим задачу правильно через сравнение периодов. Если в поле Значение хранится дата, используем условие МЕЖДУ:


ГДЕ
    Активность.Значение МЕЖДУ &НачалоДняЗавтра И &КонецДняЗавтра

Такой подход называется SARGable (Searchable Argument). Он позволяет SQL-серверу мгновенно найти нужный диапазон в индексе, не читая всю таблицу целиком.

Поиск по ссылке вместо поиска по коду

В исходном запросе используется фильтр Активность.Объект.Обещание.Код = "000000077". Сравнение строк — это всегда более трудоемкая операция для базы данных, чем сравнение ссылок. Ссылки в 1С (GUID) хранятся в бинарном виде, и поиск по ним оптимизирован на уровне движка СУБД.

Рассмотрим правильный алгоритм действий:

  1. Сначала в коде на языке 1С найдем ссылку на нужный объект по его коду.
  2. Передадим полученную ссылку в параметр запроса.

// В коде 1С:
СсылкаОбещание = Справочники.Обещания.НайтиПоКоду("000000077");

// В запросе:
ГДЕ
    ВЫРАЗИТЬ(Активность.Объект КАК Справочник.Задачи).Обещание = &СсылкаОбещание

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

Использование индексов и структура регистра

Разберем вопрос индексации. Если запрос все равно работает медленно, проверим свойства регистра в Конфигураторе. Поле Реквизит, которое используется в фильтре первым, обязательно должно иметь свойство «Индексировать» (или «Индексировать с доп. упорядочиванием»).

Если поле Значение является ресурсом, оно по умолчанию не индексируется. Для оптимизации поиска по этому полю (например, по дате) необходимо вручную установить флаг индексации в свойствах ресурса. Без индекса СУБД всегда будет выполнять полный перебор данных, какими бы красивыми ни были условия ГДЕ.

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

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

Рассмотрим пример реализации по шагам:


// Шаг 1: Отбираем только нужные записи по индексированным полям
ВЫБРАТЬ
    Активность.Объект КАК ОбъектСсылка,
    Активность.Значение КАК Дата
ПОМЕСТИТЬ ВТ_ОтфильтрованныеДанные
ИЗ
    РегистрСведений.Активность КАК Активность
ГДЕ
    Активность.Реквизит = &ИмяРеквизита // Здесь "Срок"
    И Активность.Значение МЕЖДУ &НачалоДня И &КонецДня;

// Шаг 2: Соединяем временную таблицу с мастер-данными
ВЫБРАТЬ
    ВТ.Дата,
    ВЫРАЗИТЬ(ВТ.ОбъектСсылка КАК Справочник.Задачи).Контрагент КАК Контрагент
ИЗ
    ВТ_ОтфильтрованныеДанные КАК ВТ
ГДЕ
    ВЫРАЗИТЬ(ВТ.ОбъектСсылка КАК Справочник.Задачи).Обещание = &НужноеОбещание

Преимущество этого метода: На первом шаге мы максимально сужаем выборку, используя индексы по полям Реквизит и Значение. На втором шаге тяжелые операции разыменования ссылок (через ВЫРАЗИТЬ) выполняются уже для небольшого набора данных, что существенно снижает нагрузку на сервер.

Итоговый оптимизированный запрос

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


ВЫБРАТЬ
    Активность.Значение КАК Дата,
    ВЫРАЗИТЬ(Активность.Объект КАК Справочник.Задачи).Контрагент КАК Контрагент
ИЗ
    РегистрСведений.Активность КАК Активность
ГДЕ
    Активность.Реквизит = &ИмяРеквизита
    И Активность.Значение МЕЖДУ &НачалоПериода И &КонецПериода
    И ВЫРАЗИТЬ(ВЫРАЗИТЬ(Активность.Объект КАК Справочник.Задачи).Обещание КАК Справочник.Обещания) = &НужноеОбещание

Проанализируем, что изменилось:

  1. Мы избавились от тяжелого приведения типа к строке в секции ГДЕ.
  2. Мы заменили медленный оператор ПОДОБНО на эффективный диапазон МЕЖДУ.
  3. Мы явно указали типы для составных полей через ВЫРАЗИТЬ, минимизировав количество JOIN в SQL-запросе.
  4. Мы перешли от поиска по строковому коду к поиску по ссылке.

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

← На главную