Как исправить неоптимальный SQL-запрос динамического списка с лишними ISNULL и LEFT JOIN

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

При работе с динамическими списками в 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда платформа генерирует крайне тяжелые и неоптимальные SQL-запросы. Типичный пример такой "дичи" — это самосоединение основной таблицы (LEFT OUTER JOIN таблицы самой с собой) и использование функции ISNULL в секции WHERE. Это приводит к тому, что SQL Server не может эффективно использовать индексы (проблема SARGability), выполняет полное сканирование таблиц и вызывает значительные задержки при скроллинге списка. Разберем подробно, почему это происходит и как привести запросы к адекватному виду.

Выясняем причину: почему появляется самосоединение и ISNULL

Проанализируем структуру запроса, который генерирует платформа. Если мы видим конструкцию вида FROM dbo._Document272 T1 LEFT OUTER JOIN dbo._Document272 T2 ON T1._IDRRef = T2._IDRRef, это означает, что система компоновки данных (СКД), лежащая в основе динамического списка, по какой-то причине решила извлечь данные одного и того же объекта из двух разных "источников".

Основная причина такого поведения в 1С — сортировка по полю Ссылка. В платформе 1С поле Ссылка документа не является просто идентификатором. При сортировке по ссылке система фактически выполняет сортировку по двум полям: Дата + Ссылка. Чтобы обеспечить корректный порядок вывода данных (особенно при динамическом считывании порциями), СКД добавляет левое соединение для получения актуальных значений даты и ссылки, даже если эти поля уже есть в выборке. Использование ISNULL в условиях WHERE (например, ISNULL(T2._Date_Time, '2001-01-01') < @P2) — это "страховка" компоновщика на случай, если в результате LEFT JOIN правая часть окажется пустой. Для SQL-сервера это фатально: индекс по дате перестает работать, так как значение каждого поля предварительно обрабатывается функцией.

Шаг 1: Проверка пользовательских настроек (режим 1С:Предприятие)

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

  1. Откроем форму со списком в режиме 1С:Предприятие.
  2. Перейдем в меню Еще — Настроить список.
  3. Проверим вкладку Порядок. Если в списке сортировки присутствует поле Ссылка, это и есть источник проблемы.
  4. Попробуем удалить Ссылка из сортировки и заменить её на конкретное поле, например, Дата.

Как показывает практика, удаление ссылки из активной сортировки мгновенно заставляет движок 1С генерировать чистый запрос без лишних соединений.

Шаг 2: Ограничение доступных полей в схеме запроса

Если мы хотим запретить пользователям совершать такие ошибки или принудительно очистить запрос от лишних данных, воспользуемся расширением языка запросов для СКД. Рассмотрим пример, как правильно оформить текст запроса в динамическом списке, используя фигурные скобки {}.

Проанализируем ситуацию: нам нужно явно указать платформе, какие поля можно использовать для отборов и компоновки, а какие — нет. Используем конструкцию:


ВЫБРАТЬ
    Документ1.Ссылка КАК Ссылка,
    Документ1.Номер КАК Номер,
    Документ1.Дата КАК Дата,
    {ВЫБРАТЬ Ссылка.*, Номер, Дата}
ИЗ
    Документ.Документ1 КАК Документ1
{ГДЕ Документ1.Ссылка.*, Документ1.Номер, Документ1.Дата}

Применение фигурных скобок {ВЫБРАТЬ} и {ГДЕ} позволяет жестко контролировать, какие поля будут участвовать в построении итогового SQL-запроса. Это исключает использование дочерних реквизитов (через точку), которые часто и вызывают нежелательные JOIN.

Шаг 3: Программная диагностика исполняемого запроса

Чтобы окончательно убедиться, какой именно запрос отправляется в СУБД после всех преобразований компоновщика, мы можем вывести "исполняемую схему" программно. Разберем полезный прием: добавим на форму временную кнопку и проанализируем результат выполнения следующего кода:


// Получаем реальный текст запроса, который строит СКД
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();

Макет = Компоновщик.Выполнить(Схема, Настройки);
ТекстЗапросаДляСУБД = Макет.НаборыДанных[0].Запрос;

Сообщить(ТекстЗапросаДляСУБД);

Этот метод позволяет увидеть запрос именно в том виде, в котором его "видит" платформа перед отправкой на SQL-сервер, включая все наложенные пользователем фильтры и сортировки. Проанализировав ТекстЗапросаДляСУБД, мы сможем точно понять, какое поле вызывает LEFT JOIN.

Шаг 4: Очистка кэша и хранилища настроек

Бывают случаи, когда программист уже исправил запрос в конфигураторе, убрал все лишние сортировки, но профайлер SQL всё равно показывает старую "дичь". Выясним причину: настройки динамических списков сохраняются в системном хранилище настроек в разрезе пользователей и полных имен форм.

Рассмотрим порядок действий для очистки:

  1. В режиме пользователя зайдите в Настройки — Очистить настройки всех пользователей (если есть права администратора).
  2. Или программно удалите настройки для конкретной формы, используя ХранилищеОбщихНастроек.
  3. В самой форме списка можно нажать Еще — Установить стандартные настройки. Это сбросит текущий КомпоновщикНастроек к состоянию, определенному в конфигураторе.

Дополнительные факторы: RLS и Динамическое считывание

Проанализируем еще два важных момента, которые могут влиять на появление ISNULL:

1. Ограничение доступа на уровне записей (RLS). Если у пользователя ограничены права, платформа будет принудительно добавлять условия в секцию WHERE для проверки прав. Эти условия практически всегда содержат ISNULL и дополнительные соединения с таблицами прав. Если под "Полными правами" запрос выглядит нормально, а под пользователем — "дико", значит, проблему нужно решать оптимизацией шаблонов RLS.

2. Свойство "Динамическое считывание данных". В свойствах динамического списка (в конфигураторе) есть флаг ДинамическоеСчитываниеДанных. Когда он включен, платформа использует специальные алгоритмы пагинации (Key-set pagination), которые требуют сравнения текущей записи с предыдущей/следующей. Именно здесь часто рождаются сложные условия в WHERE с использованием ISNULL по полям даты и ссылки для обеспечения стабильности курсора при прокрутке.

Резюме: Чтобы избавиться от лишних LEFT JOIN и ISNULL, в первую очередь откажитесь от сортировки по полю Ссылка. Используйте явную сортировку по Дата (и, если нужно, дополнительно по Номер или другому индексированному полю). Контролируйте состав доступных полей через фигурные скобки в запросе и не забывайте очищать пользовательские настройки после внесения изменений в метаданные.

← На главную