В практике разработки на платформе 1С:Предприятие часто встречается ситуация, когда простой и быстрый запрос внезапно начинает выполняться крайне медленно после добавления одной-единственной строки. Особенно ярко это проявляется при работе с полями составного типа, такими как ЛюбаяСсылка. В этой статье мы проанализируем, почему попытка оптимизации через ВЫРАЗИТЬ может привести к деградации производительности, и разберем правильные методы получения данных из составных полей.
Рассмотрим ситуацию, описанную автором: запрос к регистру сведений выполняется за 1 секунду, но при попытке получить реквизит Организация из поля ЛюбаяСсылкаТут время выполнения возрастает до 7 минут. Автор попытался использовать следующую конструкцию:
ВЫБРАТЬ
МойРегСв.*,
ВЫРАЗИТЬ(МойРегСв.ЛюбаяСсылкаТут.Организация КАК Справочник.Организации) КАК Организация
ИЗ
РегистрСведений.МойРегСв КАК МойРегСв
Давайте выясним причину такого замедления. Когда мы обращаемся через точку к полю составного типа (например, ЛюбаяСсылка), платформа 1С на уровне SQL-запроса вынуждена выполнить неявное соединение (LEFT OUTER JOIN) со всеми таблицами объектов, которые могут быть записаны в это поле. Если поле имеет тип ЛюбаяСсылка, количество таких соединений может исчисляться сотнями (все справочники и документы в системе).
Важный нюанс: В приведенном выше коде оператор ВЫРАЗИТЬ применяется к уже полученному результату обращения через точку. Это означает, что SQL-сервер сначала должен выполнить все сотни соединений, собрать огромную временную таблицу и только потом попытаться привести результат к типу Справочник.Организации. Для оптимизатора SQL-сервера построение плана такого запроса становится непосильной задачей, что и превращает 1 секунду в 7 минут.
Чтобы избежать "взрывного" роста количества соединений, мы должны указать платформе, из какой именно таблицы нужно брать данные, до того, как обратимся к реквизиту через точку. Рассмотрим правильный синтаксис:
ВЫБРАТЬ
МойРегСв.*,
ВЫРАЗИТЬ(МойРегСв.ЛюбаяСсылкаТут КАК Справочник.Договоры).Организация КАК Организация
ИЗ
РегистрСведений.МойРегСв КАК МойРегСв
В этом случае платформа понимает, что нас интересуют данные только из одной конкретной таблицы (в примере — Справочник.Договоры). SQL-запрос будет содержать всего одно соединение вместо сотен, что вернет производительность к исходным показателям.
Если поле составного типа может содержать ссылки на несколько разных типов объектов (например, Документ.Реализация и Документ.Поступление), и нам нужно получить Организацию из каждого, использование ВЫРАЗИТЬ в одном списке выбора может быть неудобным. Проанализируем более надежный подход с использованием объединения:
ВЫБРАТЬ
МойРегСв.*,
Т_Реализация.Организация КАК Организация
ИЗ
РегистрСведений.МойРегСв КАК МойРегСв
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Реализация КАК Т_Реализация
ПО МойРегСв.ЛюбаяСсылкаТут = Т_Реализация.Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
МойРегСв.*,
Т_Поступление.Организация
ИЗ
РегистрСведений.МойРегСв КАК МойРегСв
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Поступление КАК Т_Поступление
ПО МойРегСв.ЛюбаяСсылкаТут = Т_Поступление.Ссылка
Хотя такой код выглядит более громоздким, он работает максимально предсказуемо и быстро, так как каждое плечо запроса обращается к конкретной физической таблице по индексированному полю Ссылка.
Если нам заранее неизвестно, какие типы могут встретиться в поле ЛюбаяСсылка, мы можем сначала выяснить список фактически используемых типов и построить запрос динамически или использовать временные таблицы. Разберем шаги:
Это позволяет избежать ситуации, когда система пытается присоединить таблицы, данных по которым в конкретном результате запроса вообще нет.
В ходе дискуссии был затронут провокационный вопрос: почему построчная обработка результата запроса в коде 1С (через Выборка.ЛюбаяСсылкаТут.Организация) работает быстрее, чем "оптимизированный" запрос?
Проанализируем ситуацию: когда вы обращаетесь к реквизиту объекта через точку в коде 1С, платформа выполняет отдельный маленький запрос к конкретной таблице базы данных по идентификатору (GUID). Если строк всего 355, то 355 сверхбыстрых запросов по первичному ключу суммарно займут доли секунды. В то же время один "монструозный" SQL-запрос с сотнями LEFT JOIN заставляет SQL-сервер тратить колоссальные ресурсы на компиляцию плана и работу с временными данными в tempdb.
Однако помните: это справедливо только для малого количества строк. Если строк будет 100 000, "запросы в цикле" безнадежно проиграют правильно написанному запросу с использованием временных таблиц и типизации.
Для обеспечения высокой производительности системы при работе с составными типами данных рекомендуем придерживаться следующих правил:
ЛюбаяСсылка или очень широких составных типов без предварительного ограничения типа.ВЫРАЗИТЬ(Поле КАК Тип).Реквизит только тогда, когда вы точно уверены, что в поле находится именно этот тип.ОБЪЕДИНИТЬ ВСЕ с явным присоединением нужных таблиц.