Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда пользователи начинают жаловаться на медленную работу динамических списков. Особенно это заметно в крупных базах при попытке выполнить обычный контекстный поиск по началу строки или по вхождению (режим "содержит"). Кажется, что задача тривиальна, но под капотом платформы и СУБД начинают происходить странные вещи.
В этой статье мы подробно проанализируем, почему стандартный поиск в динамических списках 1С на СУБД MS SQL Server может выполняться по несколько секунд, какую роль в этом играет загадочная конструкция ESCAPE, и какими способами можно вернуть производительность системы на прежний высокий уровень.
Давайте рассмотрим конкретный живой пример. Пользователь открывает динамический список документов и выполняет контекстный поиск по реквизиту Клиент. На уровне СУБД MS SQL Server платформа 1С генерирует параметризованный запрос следующего вида:
exec sp_executesql N'
SELECT TOP 45
T1._IDRRef,
T1._Version,
T1._Fld7731RRef
FROM dbo._Document309 T1
LEFT OUTER JOIN dbo._Reference162 T2
ON T1._Fld7731RRef = T2._IDRRef
WHERE (T2._Description LIKE P1 ESCAPE ''/'')
ORDER BY (T1._Date_Time)',N'P1 nvarchar(4000)',N'%кукуку%'
Такой же запрос СУБД получает, если пользователь вручную настраивает отбор в списке с видом сравнения "Содержит". В обычных условиях этот запрос может выполняться мучительно долго — например, около 3 секунд. Однако, если мы проведем эксперимент и выполним тот же самый запрос, но вручную удалим из него конструкцию ESCAPE '/', время выполнения сократится до сотых долей секунды (0.15–0.2 секунды)!
Выясним причину такого поведения системного оптимизатора MS SQL Server и разберем по шагам, как решить эту проблему.
Для начала разберемся, зачем вообще платформа 1С добавляет конструкцию ESCAPE '/' в запрос. Это штатное и жестко зашитое поведение платформы. Оно необходимо для обеспечения безопасности и корректности поиска. Если пользователь решит ввести в строку поиска спецсимволы СУБД (например, знак процента %, нижнее подчеркивание _ или квадратные скобки [), платформа должна экранировать их с помощью указанного символа косой черты (слэша). Это предотвращает искажение логики запроса и защищает систему от потенциальных SQL-инъекций.
Однако для оптимизатора запросов MS SQL Server (Query Optimizer) наличие ESCAPE становится серьезным препятствием. Проанализируем, что происходит внутри СУБД:
LIKE без дополнительных инструкций, он может эффективно использовать гистограммы статистики по индексированному полю. Он довольно точно оценивает, сколько строк вернет данный предикат. Но как только в запросе появляется ключевое слово ESCAPE, предикат становится "непрозрачным" для стандартных механизмов оценки. Оптимизатор часто переходит на фиксированные («заглушечные») оценки вероятности.Index Seek) он выбирает полное сканирование таблицы (Index Scan или Table Scan).ORDER BY (а в динамических списках она есть практически всегда — например, по дате документа). Из-за завышенной или заниженной оценки кардинальности SQL Server может выбрать неверный тип соединения таблиц (например, ресурсоемкий Hash Join вместо легкого Nested Loops) или попытаться отсортировать огромный массив данных в памяти TempDB, что приводит к задержкам. Если убрать ORDER BY, то даже с неоптимальным планом запрос может отработать быстро, но вместе они дают синергетический эффект замедления.Поскольку штатными средствами 1С заставить динамический список принудительно убрать ESCAPE невозможно (так как логика формирования SQL-текста находится на уровне ядра платформы), нам приходится искать альтернативные пути решения проблемы.
Если текст запроса, генерируемый динамическим списком, стабилен и неизменен (меняются только значения параметров), мы можем применить механизм СУБД MS SQL Server под названием Plan Guides (Руководства планов).
Этот механизм позволяет "перехватить" запрос по его точному текстовому шаблону на стороне СУБД и принудительно навязать ему правильный план выполнения или добавить оптимизирующие подсказки (хинты) без изменения кода самой конфигурации 1С. Рассмотрим, как это сделать:
sp_create_plan_guide. Мы можем принудительно указать оптимизатору использовать конкретный индекс или задать определенный порядок соединения таблиц. Посмотрим на пример создания такого руководства:
EXEC sp_create_plan_guide
@name = N'Guide_Optimize_DocSearch',
@stmt = N'SELECT TOP 45 T1._IDRRef, T1._Version, T1._Fld7731RRef FROM dbo._Document309 T1 LEFT OUTER JOIN dbo._Reference162 T2 ON T1._Fld7731RRef = T2._IDRRef WHERE (T2._Description LIKE @P1 ESCAPE ''/'') ORDER BY (T1._Date_Time)',
@type = N'SQL',
@module_or_batch = NULL,
@params = N'@P1 nvarchar(4000)',
@hints = N'OPTION (FORCE ORDER, LOOP JOIN)';
Важное примечание: Текст запроса в параметре @stmt должен совпадать с тем, что отправляет 1С, символ в символ, включая все пробелы и переносы строк. Использование Plan Guides позволяет стабилизировать план выполнения и убрать задержки, вызванные неверной оценкой кардинальности.
Если вы работаете на современных версиях MS SQL Server (начиная с 2016 и выше), то вместо сложных манипуляций со сценарными планами можно задействовать штатный инструмент Query Store (Хранилище запросов). Это гораздо более удобная и визуальная альтернатива.
Рассмотрим порядок действий для оптимизации через Query Store:
ESCAPE в отчетах Query Store (раздел "Запросы с наибольшим потреблением ресурсов" или "Запросы с ухудшившейся производительностью").ESCAPE.Иногда исправить поведение оптимизатора на запросах с ESCAPE можно системными настройками СУБД. Проанализируем две важные настройки:
1. Флаг трассировки 4199. Этот флаг активирует все исправления оптимизатора запросов, выпущенные Microsoft в рамках накопительных обновлений. Часто именно этот флаг возвращает оптимизатору способность адекватно оценивать селективность условий с ESCAPE. Его можно включить глобально для сервера или активировать на уровне конкретной базы данных.
2. Версия Compatibility Level (Уровень совместимости базы данных). При переходе на новые версии SQL Server (например, с 2008 на 2016 или 2019) кардинально меняется модель оценки кардинальности (Cardinality Estimator). Если после обновления СУБД динамические списки стали работать медленнее, попробуйте временно вернуть уровень совместимости базы данных на предыдущее значение (например, со 130 на 110) или включить использование устаревшей модели оценки кардинальности с помощью опции базы данных:
ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = ON;
3. Регулярное обновление статистики с полной очисткой. Так как при наличии конструкции ESCAPE точность гистограмм критически важна, настройте регламентное задание на обновление статистики по проблемным таблицам с параметром FULLSCAN. Это минимизирует вероятность ошибок оптимизатора.
Если вы хотите решить проблему кардинально на уровне кода 1С и полностью отказаться от тяжелых запросов СУБД с оператором LIKE %...%, рассмотрите возможность внедрения механизмов полнотекстового поиска платформы.
Полнотекстовый поиск 1С работает по совершенно иным алгоритмам. Он задействует специализированные полнотекстовые индексы. При этом разработчик может переопределить стандартное поведение поиска в динамическом списке:
ПолнотекстовыйПоиск.
Список.Отбор.Элементы.Очистить();
ГруппаОтбора = Список.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
// Установка отбора по массиву найденных идентификаторов
ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
ЭлементОтбора.ПравоеЗначение = МассивНайденныхСсылок;
ЭлементОтбора.Использование = Истина;
Такой подход полностью исключает появление ресурсоемких SQL-запросов с конструкцией ESCAPE и позволяет осуществлять мгновенный поиск даже по базам данных объемом в терабайты.
Мы подробно проанализировали особенности работы оптимизатора MS SQL Server при получении запросов от динамических списков 1С. Подведем итог:
ESCAPE '/' добавляется платформой 1С принудительно для безопасного экранирования спецсимволов.ORDER BY.