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

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

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

Проблема: медленные соединения и неэффективная фильтрация

Рассмотрим типичную ситуацию: необходимо соединить таблицу расписания с данными о фактической записи. Основная сложность заключается в том, что соединение идет не только по ссылке (Врач), но и по периоду (вхождение даты приема в окно расписания). Когда таблицы разрастаются, стандартное ЛЕВОЕ СОЕДИНЕНИЕ начинает работать крайне медленно.

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

  1. Использование вложенных запросов в соединениях: СУБД сложно построить оптимальный план, когда данные не подготовлены заранее.
  2. Разыменование через точку: Обращения вида Врач.Специальность внутри итогов или условий заставляют SQL-сервер плодить неявные соединения.
  3. Оператор В ИЕРАРХИИ: Это тяжелая операция, которая требует обхода всего дерева справочника.
  4. Отсутствие индексов у временных таблиц: Даже если вы вынесли данные в ВТ, без индекса по полям связи скорость будет низкой.

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

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

Выясним причину тормозов: если мы напрямую соединяем таблицу расписания с регистром ЗаписьНаПрием, СУБД может пытаться просканировать весь индекс регистра. Вместо этого выберем только нужные записи за период во временную таблицу и обязательно ее проиндексируем.


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

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

Решение 2: Устранение "точек" (разыменования полей)

Проанализируем структуру итогового запроса. Если в блоках ВЫБРАТЬ, УПОРЯДОЧИТЬ ПО или ИТОГИ присутствуют поля через точку, например, Таб.Врач.Специальность, это значительно замедляет выполнение. Каждая такая точка — это дополнительное LEFT JOIN к таблице справочника на уровне SQL.

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


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

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

Решение 3: Оптимизация работы с иерархией

Оператор В ИЕРАРХИИ часто становится "бутылочным горлышком". Если вам нужно отфильтровать данные по кабинетам конкретного отделения, лучше заранее подготовить список всех кабинетов этого отделения в коде 1С и передать его в запрос как массив.

Разберем пример:


// В коде 1С
СписокКабинетов = Справочники.Кабинеты.ВыбратьИерархически(ВыбранноеОтделение);
// Заполняем массив...

// В запросе вместо:
ГДЕ ВремяПриема.Кабинет В ИЕРАРХИИ (&Отделение)

// Используем:
ГДЕ ВремяПриема.Кабинет В (&МассивКабинетов)

Решение 4: Оптимизация фильтрации по датам

В ходе анализа выяснилось, что использование функций внутри условий (например, НАЧАЛОПЕРИОДА(ЗаписьНаПрием.ДатаПриема, ДЕНЬ)) может блокировать использование индексов. Если вам нужно выбрать данные за конкретный день, используйте прямое сравнение с диапазоном или подготовьте параметры заранее.

Сравним два варианта условий:

  1. Медленно: ГДЕ НАЧАЛОПЕРИОДА(ДатаПриема, ДЕНЬ) = &НужныйДень
  2. Быстро: ГДЕ ДатаПриема МЕЖДУ &НачалоДня И &КонецДня

Решение 5: Передача Табличного Документа между сервером и клиентом

Если после оптимизации запроса отчет все равно "тормозит" при выводе, проблема может быть в передаче объекта ТабличныйДокумент с сервера на клиент.

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

Итоговый алгоритм оптимизации

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

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

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

← На главную