В практике разработки на платформе 1С часто встречается загадочная на первый взгляд ситуация: запрос к виртуальной таблице остатков, который отрабатывает мгновенно при передаче параметра типа Дата, начинает существенно «тормозить», если передать в него МоментВремени. Давайте разберем по шагам, почему это происходит, как платформа формирует SQL-запросы в обоих случаях и что мы можем сделать для оптимизации этой операции.
Для начала проанализируем, как 1С транслирует наш запрос на язык SQL. Когда мы запрашиваем остатки на определенную Дату, платформа формирует достаточно простое условие в блоке WHERE. СУБД (будь то MS SQL Server или PostgreSQL) видит четкую границу по полю Period и эффективно использует индекс по периоду, выполняя операцию Index Seek.
Однако, когда мы передаем Момент времени, ситуация кардинально меняется. МоментВремени — это не просто время, это комбинация даты и ссылки на документ (регистратор). Для СУБД это означает, что нужно найти записи не просто «по секунду», а «внутри секунды до конкретного регистратора». Проанализируем типичную структуру условия, которое генерирует платформа в этом случае:
WHERE (Period = @P1 AND (RecorderTRef > @P2 OR (RecorderTRef = @P2 AND RecorderID > @P3))) OR Period > @P1
Посмотрим внимательнее на это логическое выражение. Использование оператора OR вместе со сравнениями «больше-меньше» по составным полям (тип документа и ссылка) часто сбивает с толку оптимизатор запросов СУБД. Вместо быстрого поиска по индексу система может принять решение о полном сканировании индекса (Index Scan), что на больших таблицах приводит к колоссальным задержкам. Мы видим два поиска по таблице вместо одного, и если таблица движений велика, это становится критичным.
Выясним причину, почему проблема проявляется не у всех и не всегда. Ключевым фактором здесь является состояние таблицы итогов регистра (_AccumRegTotals). Рассмотрим механизм работы виртуальной таблицы остатков: платформа не хранит остатки на каждую секунду. Она берет ближайшие рассчитанные итоги (обычно на начало месяца) и «докручивает» к ним движения из таблицы регистра до нужной точки.
Если регистр «закрывается неидеально» (как в нашем примере), это означает, что в таблице итогов скопилось огромное количество записей с нулевыми остатками в разрезе измерений. Проанализируем последствия:
МоментВремени системе приходится фильтровать и агрегировать гораздо больше строк из таблицы итогов, чем планировалось архитектурой.OR на фоне сотен тысяч «мусорных» итогов, стоимость выполнения запроса возрастает в разы.Разделение итогов, в таблице появляется поле Splitter, что еще больше увеличивает количество физических записей, которые нужно суммировать перед наложением фильтра по моменту времени.Рассмотрим еще один важный аспект — измерения регистра. Допустим, у нас первым измерением идет Номенклатура, и по ней установлен фильтр (например, вхождение во временную таблицу из 4 позиций). Казалось бы, индекс должен работать идеально. Однако, если в запросе есть условие по другому измерению (например, Склад), которое не индексировано, эффективность поиска падает.
Если регистр «разнесло» именно в разрезе складов, а по номенклатуре индекс не применяется должным образом из-за сложности плана запроса, СУБД будет вынуждена считывать лишние данные. Важный момент: отсутствие индекса по полю с низкой селективностью (где мало уникальных значений, например, всего два склада) обычно не критично, но в сочетании с «разбухшим» регистром и сложным условием по моменту времени это может стать «последней каплей» для оптимизатора.
Разберем основные методы оптимизации, от простых к сложным.
_AccumRegTotals.МоментВремени передавать в параметры виртуальной таблицы объект Граница.
ГраницаДок = Новый Граница(ДокументСсылка.МоментВремени(), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Период", ГраницаДок);
Дату). Это быстро.Мы выяснили, что проблема медленного получения остатков на МоментВремени — это комплексная проблема, сочетающая особенности генерации SQL-кода платформой 1С и состояние базы данных. Если вы столкнулись с такой ситуацией, в первую очередь проанализируйте состояние итогов регистра. Скорее всего, регистр «разбух», и СУБД просто захлебывается в объеме данных при попытке выполнить ветвистое условие фильтрации. Компактизация таблицы итогов и проверка индексов по всем измерениям, участвующим в фильтре, — кратчайший путь к восстановлению производительности.