В процессе разработки на платформе «1С:Предприятие» перед программистом часто встает вопрос: как именно СУБД и платформа обрабатывают наши запросы? Понимание внутреннего механизма выполнения запроса — это ключ к созданию быстродействующих систем. Сегодня мы разберем одну из самых «болезненных» тем для производительности: использование регистраторов в виртуальных таблицах и коварство обращений через точку.
Начнем с фундаментального вопроса: что выполнится раньше — отбор в параметрах виртуальной таблицы или условие в секции ГДЕ? Рассмотрим эту ситуацию подробнее. Для платформы 1С существует принципиальная разница в обработке этих конструкций.
Когда мы указываем условия в параметрах виртуальной таблицы (например, РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, , )), платформа формирует запрос к СУБД таким образом, что фильтрация происходит максимально рано. СУБД получает команду выбрать данные, которые уже ограничены периодом или значениями измерений. Это позволяет значительно сократить объем данных, которые необходимо прочитать с диска и передать в память.
Если же мы переносим условия в секцию ГДЕ, ситуация меняется. Рассмотрим пример:
ВЫБРАТЬ
ВыручкаОбороты.Номенклатура,
ВыручкаОбороты.СуммаВыручкиОборот
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты() КАК ВыручкаОбороты
ГДЕ
ВыручкаОбороты.АналитикаУчетаНоменклатуры.Номенклатура = &Номенклатура
В этом случае платформа может сначала попытаться рассчитать обороты по всем записям регистра за все время (если не указаны даты), и только потом применить фильтр ГДЕ. Проанализируем последствия: запрос будет работать крайне медленно на больших таблицах, так как СУБД придется обрабатывать избыточный массив данных. Поэтому золотое правило оптимизации: всегда максимально ограничивайте выборку в параметрах виртуальной таблицы.
Многие начинающие разработчики удивляются, почему запрос к виртуальной таблице Обороты начинает «тормозить», как только в списке выбранных полей появляется Регистратор. Разберем причину этого явления.
Данные в регистрах накопления хранятся в двух видах таблиц:
Виртуальная таблица Обороты — это мощный инструмент, который умеет мгновенно брать данные из таблиц итогов. Однако в таблицах итогов нет поля Регистратор, так как итоги свернуты. Как только вы добавляете поле Регистратор или устанавливаете периодичность Авто или Регистратор, платформа вынуждена игнорировать таблицы итогов. Она обращается напрямую к тяжелой физической таблице движений и выполняет группировку «на лету».
Посмотрим на пример запроса, который лишает виртуальную таблицу ее преимуществ:
ВЫБРАТЬ
Выручка.Регистратор,
Выручка.КоличествоОборот
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&Начало, &Конец, Регистратор, ) КАК Выручка
В данном случае смысл использования виртуальной таблицы практически теряется. Платформа просто делает за вас работу по группировке записей из реальной таблицы. Если вам действительно нужен регистратор, помните: этот запрос будет выполняться значительно дольше, чем запрос за тот же период без регистратора.
Рассмотрим ситуацию из обсуждения: обращение ВыручкаИСебестоимостьПродаж.Регистратор.Организация. На первый взгляд, это удобно. Но с точки зрения SQL — это катастрофа для производительности, если Регистратор имеет составной тип данных.
Каждое обращение через точку в 1С преобразуется в оператор LEFT JOIN. Если поле Регистратор может принимать значения 50 различных типов документов, то при написании Регистратор.Организация СУБД будет вынуждена выполнить 50 соединений со всеми возможными таблицами документов, чтобы найти, в какой из них лежит нужная организация. Это порождает огромный SQL-запрос, который создает колоссальную нагрузку на сервер.
Как мы можем оптимизировать такую ситуацию? Проанализируем варианты:
Организация уже является измерением регистра. Всегда берите организацию напрямую из регистра: ВыручкаИСебестоимостьПродаж.Организация. Это поле проиндексировано и доступно мгновенно.ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Организация. Это сократит количество соединений до одного.Склада или Подразделения, которые являются измерениями, и эта связь однозначна — лучше получить организацию через них, но помните о количестве «точек». Каждая точка — это новое соединение.Существует миф, что порядок условий в ГДЕ (например, сначала ссылка, потом дата) критически важен. Выясним правду. Современные СУБД (MS SQL Server, PostgreSQL) используют оптимизатор на основе стоимостей. Перед выполнением запроса СУБД анализирует статистику данных и сама решает, какое условие применить первым.
Например, в запросе к табличной части документа:
ГДЕ
РеализацияТовары.Ссылка = &Ссылка
И РеализацияТовары.Номенклатура.ТипНоменклатуры <> ЗНАЧЕНИЕ(Перечисление.ТипыНоменклатуры.Услуга)
СУБД в любом случае сначала применит фильтр по Ссылка, так как это поле входит в кластерный индекс и позволяет мгновенно найти нужные строки. Порядок написания условий в тексте запроса здесь не играет роли.
Для сложных отборов мы рекомендуем использовать временные таблицы. Это помогает «подсказать» оптимизатору СУБД правильный путь выполнения. Рассмотрим по шагам:
Такой подход гарантирует, что СУБД не будет строить неэффективные планы выполнения при работе с большими объемами данных.
Резюмируем: Для достижения максимальной скорости запроса старайтесь избегать получения Регистратора в виртуальных таблицах, никогда не обращайтесь через точку к полям составного типа и всегда переносите фильтры в параметры виртуальных таблиц. Помните, что каждое поле, выбранное «для удобства» через точку, может стоить системе нескольких секунд ожидания.