Как ускорить запрос к полю составного типа Документ, если он работает слишком долго

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

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

Выясним причину низкой производительности

Проанализируем ситуацию с точки зрения взаимодействия платформы и СУБД. Когда мы пишем в запросе ТЧ.Документ.Дата, платформа не знает заранее, какой именно документ лежит в конкретной строке. Чтобы получить дату, SQL-запрос должен обратиться к каждой возможной таблице документа. Если в конфигурации 300 видов документов, в плане запроса будет 300 соединений. СУБД тратит колоссальное время на построение плана (парсинг) и выполнение такого гигантского объединения.

Рассмотрим ситуацию, когда база работает на PostgreSQL. У этой СУБД есть параметры join_collapse_limit и from_collapse_limit, которые по умолчанию равны 8. Если соединений больше, планировщик перестает искать оптимальный путь и выполняет запрос неэффективно. Даже на MS SQL Server огромное количество соединений в одном запросе приводит к деградации производительности.

Решение 1: Динамическое построение запроса с типизацией

Это один из самых эффективных способов. Вместо того чтобы просить СУБД соединиться со всеми таблицами сразу, мы сначала выясним, какие типы документов реально присутствуют в наших данных, а затем выполним точечные запросы. Проанализируем пример кода, реализующий этот подход:


// 1. Сначала получаем список всех ссылок и их типов во временную таблицу
ЗапросТипы = Новый Запрос;
ЗапросТипы.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   ТЧ.Документ КАК Ссылка,
    |   ТИПЗНАЧЕНИЯ(ТЧ.Документ) КАК ТипОбъекта
    |ИЗ
    |   Документ.ЗаявкаНаТранспорт.ДокументыОснования КАК ТЧ
    |ГДЕ
    |   ТЧ.Ссылка = &СсылкаЗаявки";

ЗапросТипы.УстановитьПараметр("СсылкаЗаявки", СсылкаНаЗаявку);
ТаблицаТипов = ЗапросТипы.Выполнить().Выгрузить();

// 2. Группируем ссылки по типам и собираем итоговый запрос
// Для каждого уникального типа используем оператор ВЫРАЗИТЬ
ТекстЗапроса = "";
Для Каждого СтрокаТипа Из ТаблицаТипов.Скопировать(, "ТипОбъекта") Цикл
    СтрокаТипа.ТипОбъекта = СтрокаТипа.ТипОбъекта; // Убираем дубли
КонецЦикла;
ТаблицаТипов.Свернуть("ТипОбъекта");

Для НомерТипа = 0 По ТаблицаТипов.Количество() - 1 Цикл
    ИмяТипа = ТаблицаТипов[НомерТипа].ТипОбъекта;
    
    ТекстЗапроса = ТекстЗапроса + 
        "ВЫБРАТЬ
        |   ВЫРАЗИТЬ(ТЧ.Документ КАК Документ." + ИмяТипа + ").Номер КАК Номер,
        |   ВЫРАЗИТЬ(ТЧ.Документ КАК Документ." + ИмяТипа + ").Дата КАК Дата
        |ИЗ
        |   Документ.ЗаявкаНаТранспорт.ДокументыОснования КАК ТЧ
        |ГДЕ
        |   ТЧ.Ссылка = &СсылкаЗаявки И ТЧ.Документ ССЫЛКА Документ." + ИмяТипа;
        
    Если НомерТипа < ТаблицаТипов.Количество() - 1 Тогда
        ТекстЗапроса = ТекстЗапроса + " ОБЪЕДИНИТЬ ВСЕ ";
    КонецЕсли;
КонецЦикла;

Использование оператора ВЫРАЗИТЬ заставляет платформу обращаться только к одной конкретной таблице документа в каждой части объединения. Это кардинально ускоряет выполнение.

Решение 2: Использование функций Библиотеки Стандартных Подсистем (БСП)

Если ваша конфигурация построена на базе БСП, рассмотрим более простой программный способ получения данных без написания сложных запросов вручную. Рассмотрим метод ЗначенияРеквизитовОбъектов общего модуля ОбщегоНазначения.

Посмотрим на пример реализации:


// Получаем массив ссылок из табличной части
МассивСсылок = СсылкаНаЗаявку.ДокументыОснования.ВыгрузитьКолонку("Документ");

// Используем готовую функцию БСП, которая оптимизирована для работы с составными типами
ТаблицаДанных = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивСсылок, "Номер, Дата");

Для Каждого СсылкаОснование Из МассивСсылок Цикл
    Данные = ТаблицаДанных[СсылкаОснование];
    Если Данные <> Неопределено Тогда
        Сообщить("Номер: " + Данные.Номер + ", Дата: " + Данные.Дата);
    КонецЕсли;
КонецЦикла;

Этот метод внутри себя выполняет оптимизацию, схожую с динамическим построением запроса, распределяя объекты по типам и делая запросы к соответствующим таблицам. Это избавляет от проблем с LEFT JOIN по всей конфигурации.

Решение 3: Использование регистра ДанныеПервичныхДокументов

В современных конфигурациях (ERP, КА, БП 3.0) существует специальный регистр сведений ДанныеПервичныхДокументов. Он предназначен именно для того, чтобы быстро получать номер и дату любого документа, не обращаясь к его основной таблице.

Проанализируем, как переписать запрос с использованием этого регистра:


ВЫБРАТЬ
    ТЧ.Документ КАК Ссылка,
    ДанныеДокументов.Номер КАК Номер,
    ДанныеДокументов.Дата КАК Дата
ИЗ
    Документ.ЗаявкаНаТранспорт.ДокументыОснования КАК ТЧ
ЛЕВОЕ СОЕДИНЕНИЕ
    РегистрСведений.ДанныеПервичныхДокументов КАК ДанныеДокументов
ПО
    ТЧ.Документ = ДанныеДокументов.Документ
ГДЕ
    ТЧ.Ссылка = &Ссылка

Преимущества этого подхода: мы делаем всего одно соединение с одной индексированной таблицей регистра. Это работает молниеносно даже при огромном количестве типов документов.

Решение 4: Программный перебор объектов

Иногда, как ни странно, обычный цикл по строкам табличной части может работать быстрее, чем тяжелый запрос. Рассмотрим этот вариант, если количество строк в табличной части невелико (до 50-100 штук).


Для Каждого СтрокаТЧ Из СсылкаНаЗаявку.ДокументыОснования Цикл
    // Обращение к реквизитам через точку в цикле вызовет отдельный запрос к базе
    // Но это будет запрос к ОДНОЙ конкретной таблице типа этого документа
    НомерДок = СтрокаТЧ.Документ.Номер;
    ДатаДок = СтрокаТЧ.Документ.Дата;
КонецЦикла;

Важно помнить: этот метод превращается в "запросы в цикле". Если документов много, производительность упадет. Однако для печатной формы одного документа, где оснований всего 5-10 штук, это может быть приемлемым "быстрым" решением.

Рекомендации по оптимизации структуры

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

  1. Ограничьте составной тип. Если в поле "Документ" реально могут быть только 5 типов документов, укажите их явно в метаданных вместо типа "Любая ссылка". Платформа будет строить соединения только с этими 5 таблицами.
  2. Денормализация данных. Добавьте в табличную часть ДокументыОснования реквизиты НомерОснования (Строка) и ДатаОснования (Дата). Заполняйте их при выборе документа-основания. Тогда в запросе вам вообще не придется обращаться к таблицам других документов.
  3. Индексация. Убедитесь, что реквизит табличной части Документ проиндексирован. Это поможет СУБД быстрее выполнять соединения.

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

← На главную