Почему один и тот же запрос в 1С выполняется за разное время и как это исправить?

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

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда запрос, работающий в штатном режиме за доли секунды, вдруг начинает «зависать» на минуту или более. Особенно критично это становится, когда такой запрос выполняется в транзакции проведения важных документов, например, «Реализации товаров и услуг». В результате работа пользователей полностью блокируется, возникают таймауты и взаимные блокировки СУБД.

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

Анализ исходной проблемы

Рассмотрим исходный запрос, который периодически вызывает сильные задержки при проведении документов:


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

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

Причина №1. Механизм Parameter Sniffing и конструкция ВЫБОР

Главным виновником нестабильного поведения запроса является использование конструкции ВЫБОР внутри параметров виртуальной таблицы и в секции ГДЕ. Когда СУБД (будь то MS SQL Server или PostgreSQL) впервые компилирует данный запрос, она анализирует переданные значения параметров — этот процесс называется Parameter Sniffing (прослушивание параметров).

Выясним, что происходит на уровне СУБД при разных сценариях:

  1. Сценарий 1 (рублевый учет): В запрос передается параметр &Валютный = Ложь. Оптимизатор СУБД строит план выполнения, ориентированный на работу только с рублевыми суммами. Этот план кэшируется для последующего использования.
  2. Сценарий 2 (валютный учет): Спустя некоторое время тот же самый запрос вызывается с параметром &Валютный = Истина. СУБД пытается сэкономить ресурсы на компиляции и берет уже готовый план из кэша. Но этот план был построен для рублевого учета! Он не учитывает необходимость фильтрации по валюте и валютным суммам.

В результате СУБД вместо быстрого поиска по индексу начинает выполнять полное сканирование таблиц (Clustered Index Scan), что увеличивает время выполнения с миллисекунд до десятков секунд или даже минут.

Конструкция ВЫБОР транслируется на язык SQL в виде операторов CASE WHEN или сложных логических условий с OR. Такие конструкции не позволяют СУБД эффективно использовать индексы, так как вычисление условий происходит динамически для каждой строки таблицы.

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

Для того чтобы полностью исключить негативное влияние Parameter Sniffing, необходимо избавиться от условного ветвления внутри самого SQL-запроса. Сделать это нужно на стороне встроенного языка 1С с помощью динамического формирования текста запроса:


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

Если Валютный Тогда
    УсловиеВалюты = " И Валюта = &Валюта";
    УсловиеСуммы  = "ПланСчетовОсновнойОстатки.ВалютнаяСуммаОстатокДт < 0";
Иначе
    УсловиеВалюты = "";
    УсловиеСуммы  = "ПланСчетовОсновнойОстатки.СуммаОстатокДт < 0";
КонецЕсли;

ТекстЗапроса = СтрЗаменить(ТекстШаблона, "%УсловиеВалюты%", УсловиеВалюты);
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "%УсловиеСуммы%", УсловиеСуммы);

Благодаря этому подходу платформа 1С будет передавать в СУБД два абсолютно разных текста запроса. Для каждого из них СУБД создаст и закэширует свой собственный, максимально оптимальный план выполнения, который никогда не перепутается.

Причина №2. Отсутствие параметра «ВидыСубконто»

Обратите внимание на третий параметр виртуальной таблицы Остатки. В исходном запросе он оставлен пустым:

РегистрБухгалтерии.ПланСчетовОсновной.Остатки(&ДатаКон, Счет = &СчетКт, , ... )

Разберем техническую суть этой ошибки. Поля Субконто1, Субконто2, Субконто3 являются универсальными. На уровне базы данных СУБД значения субконто физически хранятся в отдельной таблице значений субконто (например, таблица _AccRgED в MS SQL Server).

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

Решение: Жесткое указание видов субконто

Для оптимизации запроса необходимо явно передать массив видов субконто в параметры виртуальной таблицы. Это позволит платформе построить точечный SQL-запрос с прямыми и эффективными соединениями таблиц:


МассивВидовСубконто = Новый Массив;
МассивВидовСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты);
МассивВидовСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры);
МассивВидовСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.СтавкиНДС);

Запрос.УстановитьПараметр("ВидыСубконто", МассивВидовСубконто);

Передача этого параметра гарантирует задействование СУБД оптимального индекса по субконто вида (Счет, ВидСубконто1, Значение1...).

Причина №3. Нестабильность условия «В (&СписокСтавокНДС)»

Использование оператора сравнения В со списком значений в параметрах виртуальной таблицы — еще одна частая причина дестабилизации планов СУБД. Если количество элементов в передаваемом списке (массиве) постоянно меняется (например, при одном вызове передается 1 элемент, а при другом — 5), СУБД может посчитать такие запросы разными и начать заново строить планы выполнения, либо применить неэффективный кэшированный план.

Решение: Использование временных таблиц

Если список значений содержит более одного-двух элементов, правильным решением будет предварительная выгрузка списка во временную таблицу с ее обязательным индексированием:


// Создаем временную таблицу со списком ставок НДС
Запрос.Текст = 
"ВЫБРАТЬ
|    Ставки.Ссылка КАК СтавкаНДС
|ПОМЕСТИТЬ ВТ_СтавкиНДС
|ИЗ
|    &СписокСтавокНДС КАК Ставки
|ИНДЕКСИРОВАТЬ ПО
|    СтавкаНДС";

После этого в параметрах виртуальной таблицы остатков мы можем связать данные через условие Субконто3 В (ВЫБРАТЬ СтавкаНДС ИЗ ВТ_СтавкиНДС). Это сделает структуру SQL-запроса статичной и предсказуемой для планировщика СУБД независимо от количества элементов в списке.

Причина №4. Проблемы с регламентным обслуживанием СУБД

Если плавающее замедление происходит строго периодически (например, 1-2 раза в месяц), это явно указывает на периоды пиковых нагрузок: закрытие отчетного периода, массовое перепроведение документов или сдача отчетности.

В эти моменты в базу данных записываются миллионы новых строк. Статистика СУБД по распределению данных в таблицах мгновенно устаревает. На основе «тухлой» (устаревшей) статистики оптимизатор СУБД начинает ошибочно полагать, что индексы неселективны, и переходит на полное сканирование таблиц.

Решение: Настройка регламентных операций СУБД

Для предотвращения подобных ситуаций системному администратору необходимо настроить регламентные задачи на сервере СУБД:

  1. Обновление статистики: В периоды интенсивной работы (закрытие месяца) обновление статистики должно выполняться ежедневно (а для особо нагруженных таблиц регистров — несколько раз в день). Для MS SQL Server используется команда UPDATE STATISTICS, для PostgreSQL — команда ANALYZE.
  2. Реиндексация и дефрагментация: Регулярное перестроение индексов (REBUILD / REORGANIZE) позволяет СУБД поддерживать физическую структуру таблиц в оптимальном состоянии, что ускоряет выборку данных.

Итоги

Мы подробно проанализировали проблему разного времени выполнения запросов в 1С. Для обеспечения стабильной и быстрой работы системы придерживайтесь следующих правил:

← На главную