При разработке отчетов и обработок в 1С разработчики нередко сталкиваются с ситуацией, когда запрос, использующий объединение таблиц или виртуальные таблицы регистра сведений, зависает либо выполняется крайне медленно на рабочей базе, хотя на тестовой копии отрабатывает за доли секунды. Разберем типичные ошибки при построении таких запросов и рассмотрим практические способы их устранения.
Рассмотрим исходную ситуацию. В коде выполняется объединение двух выборок из одной и той же виртуальной таблицы СрезПоследних регистра сведений ЖурналУчетаБСО:
ВЫБРАТЬ
ЖурналУчетаБСОСрезПоследних.Серия КАК Серия,
ЖурналУчетаБСОСрезПоследних.НомерБланка КАК НомерБланка,
ЖурналУчетаБСОСрезПоследних.МОЛ
ПОМЕСТИТЬ вт_бланки
ИЗ
РегистрСведений.ЖурналУчетаБСО.СрезПоследних(&дата2, ) КАК ЖурналУчетаБСОСрезПоследних
ГДЕ
ЖурналУчетаБСОСрезПоследних.МОЛ = &Контрагент
И ЖурналУчетаБСОСрезПоследних.СтатусБСО = ЗНАЧЕНИЕ(Перечисление.СтатусБСО.Использован)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЖурналУчетаБСОСрезПоследних.Серия,
ЖурналУчетаБСОСрезПоследних.НомерБланка,
ЖурналУчетаБСОСрезПоследних.Регистратор.МОЛотправитель
ИЗ
РегистрСведений.ЖурналУчетаБСО.СрезПоследних(&дата2, Регистратор ССЫЛКА Документ.ДвижениеБСО И Регистратор.МОЛотправитель = &контрагент) КАК ЖурналУчетаБСОСрезПоследних
ГДЕ
ЖурналУчетаБСОСрезПоследних.СтатусБСО = ЗНАЧЕНИЕ(Перечисление.СтатусБСО.Использован);
Проанализируем ключевые узкие места данного запроса:
Регистратор в параметрах виртуальной таблицы. Поле Регистратор подчиненного регистра сведений почти всегда имеет составной тип. Конструкция Регистратор.МОЛотправитель заставляет платформу строить неявные левые соединения (LEFT JOIN) со всеми таблицами документов, являющихся регистраторами. Когда это происходит внутри параметров виртуальной таблицы СрезПоследних, СУБД (особенно Oracle и PostgreSQL) строит крайне неоптимальный план выполнения подзапроса с вычислением максимального периода, что приводит к полному зависанию.ГДЕ вместо параметров виртуальной таблицы. В первой части запроса условие по МОЛ и статусу указано в секции ГДЕ. Это означает, что платформа сначала рассчитывает срез последних по всей таблице регистра без ограничений, и лишь затем отсекает лишние записи.Если нам необходимо получить записи по одной дате среза, но с альтернативными условиями (по материально ответственному лицу или по отправителю в регистраторе), объединять две тяжелые виртуальные таблицы через ОБЪЕДИНИТЬ ВСЕ не требуется. Перенесем все условия непосредственно в параметры таблицы СрезПоследних.
Посмотрим на пример оптимизированного запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЖурналУчетаБСОСрезПоследних.Серия КАК Серия,
ЖурналУчетаБСОСрезПоследних.НомерБланка КАК НомерБланка
ПОМЕСТИТЬ вт_бланки
ИЗ
РегистрСведений.ЖурналУчетаБСО.СрезПоследних(
&Дата2,
(МОЛ = &Контрагент
ИЛИ (Регистратор ССЫЛКА Документ.ДвижениеБСО
И ВЫРАЗИТЬ(Регистратор КАК Документ.ДвижениеБСО).МОЛотправитель = &Контрагент))
И СтатусБСО = ЗНАЧЕНИЕ(Перечисление.СтатусБСО.Использован)
) КАК ЖурналУчетаБСОСрезПоследних;
Важные моменты этого варианта:
ВЫРАЗИТЬ(...) обязательно, если у поля Регистратор более одного типа. Это указывает платформе на соединение только с одной таблицей документа ДвижениеБСО вместо всех существующих регистраторов.Если в регистре накоплен большой объем записей, обращение к реквизитам документов даже через ВЫРАЗИТЬ внутри параметров среза последних может приводить к снижению скорости. Наиболее надежный с точки зрения производительности подход — предварительно отобрать ссылки на документы во временную таблицу.
Разберем по шагам реализацию данного подхода:
ДвижениеБСО, удовлетворяющие условию по отправителю, в отдельную временную таблицу с индексацией по ссылке.В.
// Шаг 1: Отбираем подходящие документы
ВЫБРАТЬ
ДвижениеБСО.Ссылка КАК Ссылка
ПОМЕСТИТЬ вт_НужныеДокументы
ИЗ
Документ.ДвижениеБСО КАК ДвижениеБСО
ГДЕ
ДвижениеБСО.МОЛотправитель = &Контрагент
И ДвижениеБСО.Проведен = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
// ШАГ 2: Выбираем срез с фильтром по измерениям и списку документов
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЖурналУчетаБСО.Серия КАК Серия,
ЖурналУчетаБСО.НомерБланка КАК НомерБланка
ИЗ
РегистрСведений.ЖурналУчетаБСО.СрезПоследних(
&Дата2,
(МОЛ = &Контрагент ИЛИ Регистратор В (ВЫБРАТЬ вт_НужныеДокументы.Ссылка ИЗ вт_НужныеДокументы))
И СтатусБСО = ЗНАЧЕНИЕ(Перечисление.СтатусБСО.Использован)
) КАК ЖурналУчетаБСО;
Такой подход полностью исключает неявные соединения и сложные вычисления внутри подзапроса среза, обеспечивая стабильный и предсказуемый план запроса на любых СУБД.
Если после исправления текста запроса на рабочей базе все еще наблюдаются задержки, выполним следующие диагностические действия:
МОЛотправитель выполняется регулярно, имеет смысл добавить это поле непосредственно в состав измерений или реквизитов регистра сведений при проведении, чтобы не обращаться к шапке документа через регистратор.