В процессе доработки и сопровождения конфигурации «1С:Зарплата и Управление Персоналом 2.5 КОРП» разработчики часто сталкиваются с существенным падением производительности при расчете времени или формировании отчетов. Одной из главных причин медленной работы является неоптимальное построение запросов к тяжелым физическим таблицам СУБД. В данной статье мы подробно разберем, почему исходный запрос к регистру сведений ГрафикиРаботыПоВидамВремени работает медленно, проанализируем структуру индексов СУБД, рассмотрим влияние составных типов данных на производительность и пошагово оптимизируем запрос для достижения максимального быстродействия.
Давайте детально рассмотрим исходный вариант запроса, который вызывает длительные задержки при выполнении:
ВЫБРАТЬ
ГрафикиРаботыПоВидамВремени.Дата КАК Период,
ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Ссылка КАК Сотрудник,
СУММА(ГрафикиРаботыПоВидамВремени.ОсновноеЗначение) КАК ЧасовЯвки
ИЗ
РегистрСведений.ГрафикиРаботыПоВидамВремени КАК ГрафикиРаботыПоВидамВремени
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РабочееВремяРаботников КАК РабочееВремяРаботников
ПО ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Ссылка = РабочееВремяРаботников.Сотрудник
И ГрафикиРаботыПоВидамВремени.Дата = РабочееВремяРаботников.Период
ГДЕ
ГрафикиРаботыПоВидамВремени.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Организация = &Организация
И ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Ссылка В
(ВЫБРАТЬ
Т.Сотрудник
ИЗ
ВТСотрудникиПодразделения КАК Т)
И ГрафикиРаботыПоВидамВремени.ВидУчетаВремени = ЗНАЧЕНИЕ(Перечисление.ВидыУчетаВремени.ПоЧасам)
И ГрафикиРаботыПоВидамВремени.План = ЛОЖЬ
СГРУППИРОВАТЬ ПО
ГрафикиРаботыПоВидамВремени.Дата,
ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Ссылка
При детальном анализе этого кода мы можем выделить несколько критических уязвимостей, которые заставляют СУБД выполнять полное сканирование огромной таблицы (операция Index Scan или Table Scan) вместо быстрого поиска по индексу (Index Seek):
ГрафикРаботы (которое в ЗУП 2.5 имеет составной тип) заставляет платформу генерировать тяжелые внешние соединения LEFT OUTER JOIN ко всем физическим таблицам, входящим в этот тип.ГрафикиРаботыПоВидамВремени имеет строго определенный порядок измерений в индексе на уровне СУБД. В исходном запросе отсутствует важнейшее измерение, стоящее в начале индекса — Месяц.В (IN) с подзапросом к временной таблице: Подобные конструкции часто приводят к неоптимальному плану выполнения (особенно в СУБД PostgreSQL или при устаревшей статистике в MS SQL Server).Ссылка у ссылочных полей: Конструкция ГрафикРаботы.Ссылка избыточна и заставляет платформу выполнять лишнее неявное обращение к таблице справочника.В конфигурации ЗУП 2.5 измерение ГрафикРаботы регистра сведений ГрафикиРаботыПоВидамВремени имеет составной тип. Оно может хранить ссылки как на справочник ГрафикиРаботы, так и на справочник СотрудникиОрганизаций (индивидуальные графики).
Когда мы пишем:
ГрафикиРаботыПоВидамВремени.ГрафикРаботы.Организация
Платформа 1С не знает заранее, какой именно тип там хранится в конкретной строке, поэтому генерирует SQL-запрос, который соединяет физическую таблицу регистра с таблицами всех справочников, входящих в составной тип. Это приводит к колоссальному падению производительности.
Для решения этой проблемы необходимо использовать функцию ВЫРАЗИТЬ, которая явно указывает СУБД, к какому типу приводить данное поле:
ВЫРАЗИТЬ(ГрафикиРаботыПоВидамВремени.ГрафикРаботы КАК Справочник.СотрудникиОрганизаций)
Важное замечание: Обратите внимание на распространенную ошибку разработчиков, переходящих с ЗУП 3 на ЗУП 2.5. В редакции 2.5 данный справочник называется именно СотрудникиОрганизаций, а не Сотрудники. Попытка привести поле к несуществующему типу Справочник.Сотрудники вызовет ошибку метаданных при выполнении запроса.
Регистр сведений ГрафикиРаботыПоВидамВремени является независимым и непериодическим. Порядок измерений в его уникальном индексе на уровне СУБД выглядит следующим образом:
ГрафикРаботыМесяц (дата начала месяца)План (булево)ВидУчетаВремениДатаЧтобы СУБД могла эффективно использовать этот индекс и выполнять операцию быстрого поиска по индексу (Index Seek), мы должны передавать в условия запроса поля строго по порядку их следования в индексе.
В исходном запросе мы делаем отбор по первому измерению (ГрафикРаботы), но затем полностью пропускаем второе измерение — Месяц, и сразу переходим к отборам по План, ВидУчетаВремени и Дата (5-е измерение). Из-за пропуска поля Месяц СУБД не может выполнить поиск по индексу и переходит к сканированию всей таблицы. Для исправления этой ситуации нам нужно обязательно добавить в условия ГДЕ отбор по измерению Месяц:
И ГрафикиРаботыПоВидамВремени.Месяц МЕЖДУ &НачалоМесяца И &КонецМесяца
Конструкция с подзапросом:
ГрафикРаботы В (ВЫБРАТЬ Т.Сотрудник ИЗ ВТСотрудникиПодразделения КАК Т)
должна быть заменена на полноценное ВНУТРЕННЕЕ СОЕДИНЕНИЕ с временной таблицей ВТСотрудникиПодразделения. Внутреннее соединение обрабатывается оптимизатором СУБД гораздо эффективнее и позволяет построить оптимальный план выполнения.
Кроме того, временные таблицы, участвующие в соединениях (ВТСотрудникиПодразделения и РабочееВремяРаботников), обязательно должны быть проиндексированы по полям связи. Перед выполнением основного запроса убедитесь, что при создании этих ВТ вы указали инструкции индексации:
ИНДЕКСИРОВАТЬ ПО Сотрудник, Период
Соберем все наши изменения воедино. Мы выполним следующие действия:
ВЫРАЗИТЬ для поля ГрафикРаботы, чтобы избежать разыменования составного типа.Месяц, чтобы задействовать индекс регистра.Ссылка (вместо ГрафикРаботы.Ссылка будем использовать просто поле ГрафикРаботы).Посмотрим на итоговый оптимизированный вариант запроса:
ВЫБРАТЬ
ГрафикиРаботыПоВидамВремени.Дата КАК Период,
ВЫРАЗИТЬ(ГрафикиРаботыПоВидамВремени.ГрафикРаботы КАК Справочник.СотрудникиОрганизаций) КАК Сотрудник,
СУММА(ГрафикиРаботыПоВидамВремени.ОсновноеЗначение) КАК ЧасовЯвки
ИЗ
РегистрСведений.ГрафикиРаботыПоВидамВремени КАК ГрафикиРаботыПоВидамВремени
// Внутреннее соединение вместо оператора "В (ВЫБРАТЬ...)"
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТСотрудникиПодразделения КАК ВТСотрудникиПодразделения
ПО ГрафикиРаботыПоВидамВремени.ГрафикРаботы = ВТСотрудникиПодразделения.Сотрудник
// Второе внутреннее соединение с проиндексированной ВТ рабочего времени
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РабочееВремяРаботников КАК РабочееВремяРаботников
ПО ГрафикиРаботыПоВидамВремени.ГрафикРаботы = РабочееВремяРаботников.Сотрудник
И ГрафикиРаботыПоВидамВремени.Дата = РабочееВремяРаботников.Период
ГДЕ
// Обязательный отбор по второму измерению индекса (Месяц) для активации Index Seek
ГрафикиРаботыПоВидамВремени.Месяц МЕЖДУ &НачалоМесяца И &КонецМесяца
// Остальные условия отбора по индексу
И ГрафикиРаботыПоВидамВремени.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И ГрафикиРаботыПоВидамВремени.ВидУчетаВремени = ЗНАЧЕНИЕ(Перечисление.ВидыУчетаВремени.ПоЧасам)
И ГрафикиРаботыПоВидамВремени.План = ЛОЖЬ
СГРУППИРОВАТЬ ПО
ГрафикиРаботыПоВидамВремени.Дата,
ВЫРАЗИТЬ(ГрафикиРаботыПоВидамВремени.ГрафикРаботы КАК Справочник.СотрудникиОрганизаций)
Для того чтобы данный запрос отработал максимально быстро, перед его запуском во фрагменте кода 1С необходимо правильно подготовить параметры &НачалоМесяца и &КонецМесяца. Их можно легко получить с помощью стандартных функций платформы:
НачалоМесяцаПараметр = НачалоМесяца(НачалоПериода);
КонецМесяцаПараметр = КонецМесяца(КонецПериода);
Запрос.УстановитьПараметр("НачалоМесяца", НачалоМесяцаПараметр);
Запрос.УстановитьПараметр("КонецМесяца", КонецМесяцаПараметр);
Также обратите внимание, что отбор по организации (ГрафикРаботы.Организация = &Организация), который присутствовал в исходном запросе в секции ГДЕ, мы исключили. Настоятельно рекомендуется наложить данный отбор на этапе формирования временной таблицы ВТСотрудникиПодразделения. Фильтрация сотрудников по организации во временной таблице выполняется мгновенно, и в основной тяжелый запрос к регистру сведений попадут уже только те сотрудники, которые гарантированно принадлежат нужной организации. Это избавляет систему от необходимости обращаться к реквизитам справочника внутри тяжелого соединения.
Применяя эти фундаментальные правила оптимизации СУБД и платформы 1С, вы сможете ускорить выполнение подобных запросов в десятки и сотни раз, снизив нагрузку на сервер и повысив общую отзывчивость вашей информационной системы.