Почему запрос в 1С замедляется при использовании ВЫРАЗИТЬ с составным типом и как правильно оптимизировать обращение через точку?

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

В практике разработки на платформе 1С:Предприятие часто встречается ситуация, когда простой и быстрый запрос внезапно начинает выполняться крайне медленно после добавления одной-единственной строки. Особенно ярко это проявляется при работе с полями составного типа, такими как ЛюбаяСсылка. В этой статье мы проанализируем, почему попытка оптимизации через ВЫРАЗИТЬ может привести к деградации производительности, и разберем правильные методы получения данных из составных полей.

Проблема: Ловушка неявных соединений

Рассмотрим ситуацию, описанную автором: запрос к регистру сведений выполняется за 1 секунду, но при попытке получить реквизит Организация из поля ЛюбаяСсылкаТут время выполнения возрастает до 7 минут. Автор попытался использовать следующую конструкцию:


ВЫБРАТЬ
    МойРегСв.*,
    ВЫРАЗИТЬ(МойРегСв.ЛюбаяСсылкаТут.Организация КАК Справочник.Организации) КАК Организация 
ИЗ
    РегистрСведений.МойРегСв КАК МойРегСв

Давайте выясним причину такого замедления. Когда мы обращаемся через точку к полю составного типа (например, ЛюбаяСсылка), платформа 1С на уровне SQL-запроса вынуждена выполнить неявное соединение (LEFT OUTER JOIN) со всеми таблицами объектов, которые могут быть записаны в это поле. Если поле имеет тип ЛюбаяСсылка, количество таких соединений может исчисляться сотнями (все справочники и документы в системе).

Важный нюанс: В приведенном выше коде оператор ВЫРАЗИТЬ применяется к уже полученному результату обращения через точку. Это означает, что SQL-сервер сначала должен выполнить все сотни соединений, собрать огромную временную таблицу и только потом попытаться привести результат к типу Справочник.Организации. Для оптимизатора SQL-сервера построение плана такого запроса становится непосильной задачей, что и превращает 1 секунду в 7 минут.

Решение №1: Правильная типизация исходного поля

Чтобы избежать "взрывного" роста количества соединений, мы должны указать платформе, из какой именно таблицы нужно брать данные, до того, как обратимся к реквизиту через точку. Рассмотрим правильный синтаксис:


ВЫБРАТЬ
    МойРегСв.*,
    ВЫРАЗИТЬ(МойРегСв.ЛюбаяСсылкаТут КАК Справочник.Договоры).Организация КАК Организация 
ИЗ
    РегистрСведений.МойРегСв КАК МойРегСв

В этом случае платформа понимает, что нас интересуют данные только из одной конкретной таблицы (в примере — Справочник.Договоры). SQL-запрос будет содержать всего одно соединение вместо сотен, что вернет производительность к исходным показателям.

Решение №2: Использование конструкции ОБЪЕДИНИТЬ ВСЕ

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


ВЫБРАТЬ
    МойРегСв.*,
    Т_Реализация.Организация КАК Организация
ИЗ
    РегистрСведений.МойРегСв КАК МойРегСв
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Реализация КАК Т_Реализация
    ПО МойРегСв.ЛюбаяСсылкаТут = Т_Реализация.Ссылка

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    МойРегСв.*,
    Т_Поступление.Организация
ИЗ
    РегистрСведений.МойРегСв КАК МойРегСв
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Поступление КАК Т_Поступление
    ПО МойРегСв.ЛюбаяСсылкаТут = Т_Поступление.Ссылка

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

Решение №3: Предварительная фильтрация по типам

Если нам заранее неизвестно, какие типы могут встретиться в поле ЛюбаяСсылка, мы можем сначала выяснить список фактически используемых типов и построить запрос динамически или использовать временные таблицы. Разберем шаги:

  1. Выберем все уникальные ссылки из регистра во временную таблицу.
  2. Проанализируем типы этих ссылок.
  3. Для каждого типа выполним отдельное получение данных.

Это позволяет избежать ситуации, когда система пытается присоединить таблицы, данных по которым в конкретном результате запроса вообще нет.

Когда "Запрос в цикле" может быть оправдан?

В ходе дискуссии был затронут провокационный вопрос: почему построчная обработка результата запроса в коде 1С (через Выборка.ЛюбаяСсылкаТут.Организация) работает быстрее, чем "оптимизированный" запрос?

Проанализируем ситуацию: когда вы обращаетесь к реквизиту объекта через точку в коде 1С, платформа выполняет отдельный маленький запрос к конкретной таблице базы данных по идентификатору (GUID). Если строк всего 355, то 355 сверхбыстрых запросов по первичному ключу суммарно займут доли секунды. В то же время один "монструозный" SQL-запрос с сотнями LEFT JOIN заставляет SQL-сервер тратить колоссальные ресурсы на компиляцию плана и работу с временными данными в tempdb.

Однако помните: это справедливо только для малого количества строк. Если строк будет 100 000, "запросы в цикле" безнадежно проиграют правильно написанному запросу с использованием временных таблиц и типизации.

Резюме и рекомендации

Для обеспечения высокой производительности системы при работе с составными типами данных рекомендуем придерживаться следующих правил:

← На главную