Почему запрос в динамическом списке 1С работает медленно и откуда в плане SQL берутся миллионы строк при небольших таблицах?

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

В практике разработчика 1С периодически возникают ситуации, когда, казалось бы, простой запрос к небольшой таблице начинает выполняться десятки секунд. При анализе плана выполнения SQL Server мы можем увидеть пугающие цифры: система сообщает о сканировании десятков миллионов строк, хотя в самой таблице их всего пара сотен тысяч. В этой статье мы разберем конкретный кейс на базе конфигурации "Альфа-Авто", выясним математику появления "лишних" строк в плане и научимся оптимизировать такие запросы.

Суть проблемы: когда статистика не врет, но пугает

Рассмотрим ситуацию: в управляемой форме используется динамический список. Основной запрос выполняет ЛЕВОЕ СОЕДИНЕНИЕ между таблицей Документ.ЗаказНаряд и Документ.УРВ_ПакетРабот. В таблице УРВ_ПакетРабот всего 143 000 записей. Однако в плане выполнения SQL мы видим, что фактическое количество обработанных строк составляет более 61 миллиона.

Первое, что приходит в голову — устаревшая статистика или отсутствие индексов. Но даже принудительное обновление статистики и обслуживание индексов не меняют картину. Разберемся, почему это происходит.

Математика вложенных циклов (Nested Loops)

Проанализируем план запроса глубже. Главный секрет кроется в операторе Nested Loops (вложенные циклы). Этот механизм работает по следующему принципу: для каждой строки, полученной из "внешней" (ведущей) таблицы, SQL Server выполняет поиск во "внутренней" таблице.

Разберем математику из нашего примера:

  1. Внешняя часть запроса после применения фильтров вернула 429 строк.
  2. Для каждой из этих 429 строк SQL Server был вынужден просканировать внутреннюю таблицу _Document5983 (это и есть наш УРВ_ПакетРабот), в которой 143 000 строк.
  3. Итоговое количество операций: 429 * 143 000 = 61 347 000 строк.

Таким образом, цифра в 61 миллион — это не количество данных в базе, а суммарное количество строк, которые пришлось "просмотреть" серверу в процессе выполнения циклического соединения. Это происходит, когда оптимизатор ошибочно ожидает, что из внешней таблицы придет всего 1 строка, и выбирает стратегию Nested Loops вместо более эффективного в данном случае Hash Match или Merge Join.

Проблема SARGability и использование конструкции CASE

Одной из причин, почему оптимизатор SQL Server не может эффективно использовать индексы, является использование условий, которые делают запрос не-SARGable (Search ARGumentable). В нашем примере в секции ГДЕ или в условии соединения используется конструкция:


ВЫБОР 
    КОГДА УРВ_ПакетРабот.Проведен ТЫГДА ЛОЖЬ 
    ИНАЧЕ ИСТИНА 
КОНЕЦ

Когда мы оборачиваем поле в ВЫБОР КОГДА (CASE на уровне SQL), мы лишаем оптимизатор возможности выполнить быстрый поиск по индексу (Index Seek). Вместо этого сервер выполняет полное сканирование (Index Scan), что в сочетании с Nested Loops и приводит к катастрофическому падению производительности.

Решение: заменим сложный выбор на прямое логическое условие. Рассмотрим пример оптимизации запроса:


ВЫБРАТЬ ПЕРВЫЕ 1000
    Т.Ссылка
ИЗ
    Документ.ЗаказНаряд КАК Т
ВНУТРЕННЕЕ СОЕДИНЕНИЕ 
    Документ.УРВ_ПакетРабот КАК Т2 
ПО 
    Т.Ссылка = Т2.ДокументОснование 
ГДЕ 
    Т2.Проведен = ЛОЖЬ

Прямое сравнение Т2.Проведен = ЛОЖЬ позволяет SQL Server прозрачно увидеть селективность данных и использовать индекс по полю Проведен.

Специфика динамических списков: коварство ПЕРВЫЕ (TOP)

В теме обсуждения прозвучал важный момент: использование ключевого слова ПЕРВЫЕ в тексте запроса динамического списка. Нужно понимать, что платформа 1С:Предприятие 8.3 управляет выборкой данных в динамических списках самостоятельно.

Проанализируем особенности поведения платформы:

Поиск в списке и "Parameter Sniffing"

Ситуация осложняется, когда пользователь начинает использовать поиск в динамическом списке. Рассмотрим случай, когда в запрос передается параметр типа "%215%".

Использование символа процента в начале строки поиска гарантированно приводит к Full Scan (полному сканированию таблицы), так как индексы по строковым полям в SQL Server строятся слева направо. Индекс не может помочь найти подстроку в середине текста.

Также не стоит забывать про эффект Parameter Sniffing. SQL Server кэширует план запроса, исходя из первых переданных параметров. Если первый поиск был очень специфичным (вернул 1 строку), сервер построит план с Nested Loops. Если следующий поиск будет массовым (например, по пустой строке или часто встречающейся подстроке), сервер попытается использовать старый "быстрый" план, который на больших данных превратится в "медленный".

Итоговые рекомендации по оптимизации

Подведем итоги и проанализируем шаги, которые помогут исправить ситуацию с медленным запросом:

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

Соблюдение этих правил позволит избежать появления "фантомных" миллионов строк в планах запросов и обеспечит стабильную работу интерфейса для пользователей.

← На главную