Проанализируем интересную и довольно распространенную ситуацию, когда тяжелый запрос в 1С выполняется бесконечно долго. При этом анализ плана выполнения на стороне MS SQL Server показывает, что оптимизатор изначально правильно оценивает поиск в индексах, но затем оператор Hash Match фатально ошибается, прогнозируя всего одну строку на выходе. Выясним причину такого поведения и разберем по шагам, как эффективно решить эту задачу.
Рассмотрим ситуацию подробнее. В платформе 1С выполняется запрос, который использует соединение с виртуальной таблицей среза последних (в терминах языка SQL это транслируется в глубоко вложенные подзапросы). Обновление статистики с полным сканированием (WITH FULLSCAN) и очистка процессорного кэша не приносят результата. Оптимизатор все равно выдает оценку в 1 строку.
Посмотрим на сокращенный пример SQL-запроса, который генерирует платформа для получения среза последних:
SELECT T1.Fld48271RRef
FROM (
SELECT T4._Fld48270RRef AS Fld48270RRef, T4._Fld48284RRef AS Fld48284RRef, T4._Period AS Period_,
T4._Fld48268 AS Fld48268_, T4._Fld48271RRef AS Fld48271RRef
FROM (
SELECT T3._Fld48268 AS Fld48268_, T3._Fld48269RRef AS Fld48269RRef, T3._Fld48270RRef AS Fld48270RRef,
T3._Fld48283 AS Fld48283_, T3._Fld48284RRef AS Fld48284RRef, MAX(T3._Period) AS MAXPERIOD_
FROM dbo._InfoRg48267 T3 WITH(NOLOCK)
GROUP BY T3._Fld48268, T3._Fld48269RRef, T3._Fld48270RRef, T3._Fld48283, T3._Fld48284RRef
) T2
INNER JOIN dbo._InfoRg48267 T4 WITH(NOLOCK)
ON T2.Fld48268_ = T4._Fld48268 AND T2.Fld48269RRef = T4._Fld48269RRef AND T2.Fld48270RRef = T4._Fld48270RRef
AND T2.Fld48283_ = T4._Fld48283 AND T2.Fld48284RRef = T4._Fld48284RRef AND T2.MAXPERIOD_ = T4._Period
) T1 ...
Здесь мы видим соединение сразу по 6 полям. Именно это, в сочетании со сложной архитектурой подзапросов, создает огромную проблему для механизма оценки кардинальности SQL Server.
Разберем механику работы Cardinality Estimator (CE) — компонента, отвечающего за прогнозирование количества строк. В старых версиях системы (например, SQL Server 2008 с уровнем совместимости 100), когда оптимизатор видит соединение по множеству полей (что типично для регистров с несколькими измерениями в 1С), он исходит из предположения, что данные в этих колонках абсолютно независимы друг от друга.
Математически он перемножает селективность (вероятность совпадения) для каждого из полей. Перемножение множества мелких дробей для 6 колонок дает в результате значение, стремящееся к нулю. Но поскольку вернуть 0 строк физически нельзя (а иногда оптимизатор просто считает, что вернется 0 и искусственно завышает), он округляет итоговое значение до минимума — ставит оценку в 1 строку.
Проанализируем последствия этой ошибки. Ожидая на выходе от оператора Hash Match всего 1 строку, оптимизатор SQL Server выделяет для выполнения операции минимально возможный объем оперативной памяти — так называемый Memory Grant.
На самом же деле из таблиц базы данных поступают сотни тысяч записей. Крошечная выделенная память мгновенно переполняется, и сервер вынужден на лету сбрасывать промежуточные хеш-таблицы на жесткий диск — в системную базу tempdb. Этот аварийный процесс называется Hash Spill (или Hash Warning). Поскольку операции чтения и записи на диск медленнее работы с оперативной памятью на порядки, выполнение запроса деградирует и визуально выглядит как бесконечное зависание системы.
Рассмотрим, как правильно выйти из сложившейся ситуации. Исправить проблему можно как на уровне кода 1С, так и за счет настроек самой базы данных.
Самый надежный и правильный способ помочь оптимизатору СУБД — разбить монолитный сложный запрос 1С, избавившись от глубокой вложенности.
Почему это работает: При создании физической временной таблицы SQL Server материализует данные (таблица получает префикс #temp). Система автоматически рассчитывает и создает актуальную статистику по этой новой таблице еще до того, как начнет выполнять следующий шаг соединения. В итоге к моменту финального соединения Hash Match оптимизатор точно знает реальное количество записей, запрашивает адекватный Memory Grant, и запрос отрабатывает исключительно в оперативной памяти за доли секунды.
Если нет возможности оперативно переписать код в конфигурации 1С, рассмотрим варианты оптимизации на стороне СУБД:
Подводя итоги, важно отметить, что переписывание запроса с использованием временных таблиц — это стандартная и наиболее эффективная практика разработки в 1С. Она гарантированно спасает оптимизатор запросов SQL Server от "слепых зон" и обеспечивает стабильно высокую производительность и масштабируемость системы при любых объемах данных.