Почему остатки на момент времени документа берутся дольше, чем на дату, и как это исправить?

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

В практике разработки на платформе 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). Рассмотрим механизм работы виртуальной таблицы остатков: платформа не хранит остатки на каждую секунду. Она берет ближайшие рассчитанные итоги (обычно на начало месяца) и «докручивает» к ним движения из таблицы регистра до нужной точки.

Если регистр «закрывается неидеально» (как в нашем примере), это означает, что в таблице итогов скопилось огромное количество записей с нулевыми остатками в разрезе измерений. Проанализируем последствия:

  1. Объем данных: При запросе на МоментВремени системе приходится фильтровать и агрегировать гораздо больше строк из таблицы итогов, чем планировалось архитектурой.
  2. Сложность фильтрации: Когда СУБД сталкивается со сложным условием OR на фоне сотен тысяч «мусорных» итогов, стоимость выполнения запроса возрастает в разы.
  3. Разделение итогов: Если в регистре включен режим Разделение итогов, в таблице появляется поле Splitter, что еще больше увеличивает количество физических записей, которые нужно суммировать перед наложением фильтра по моменту времени.

Проанализируем ситуацию с индексами и селективностью

Рассмотрим еще один важный аспект — измерения регистра. Допустим, у нас первым измерением идет Номенклатура, и по ней установлен фильтр (например, вхождение во временную таблицу из 4 позиций). Казалось бы, индекс должен работать идеально. Однако, если в запросе есть условие по другому измерению (например, Склад), которое не индексировано, эффективность поиска падает.

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

Как решить проблему: пошаговая инструкция

Разберем основные методы оптимизации, от простых к сложным.

  1. Лечение регистра (срезка итогов): Это самое действенное средство. Необходимо программно или с помощью стандартных регламентных процедур «схлопнуть» движения так, чтобы убрать записи с нулевыми остатками. Если регистр накопления «раздулся», никакие индексы не помогут так, как очистка таблицы _AccumRegTotals.
  2. Использование объекта Граница: Попробуем вместо МоментВремени передавать в параметры виртуальной таблицы объект Граница.
    
    ГраницаДок = Новый Граница(ДокументСсылка.МоментВремени(), ВидГраницы.Включая);
    Запрос.УстановитьПараметр("Период", ГраницаДок);
    
    Иногда изменение вида границы помогает платформе сформировать чуть более простой SQL-запрос, который легче переваривается оптимизатором СУБД.
  3. Обновление статистики: Хотя в сообщении форума упоминалось, что это «сделало только хуже», в нормальных условиях актуальная статистика помогает оптимизатору выбрать правильный план. Если после пересчета стало хуже — это явный признак того, что план запроса стал нестабильным (Parameter Sniffing) или структура регистра требует немедленного обслуживания.
  4. Ручное докручивание остатков (Extreme Optimization): В самых тяжелых случаях, когда виртуальная таблица не справляется, мы можем применить следующий алгоритм:
    • Получаем остатки на начало секунды (передаем Дату). Это быстро.
    • Отдельным запросом получаем обороты по таблице движений от начала этой секунды до нашего регистратора.
    • Складываем результаты в коде или во временной таблице.
    Два простых запроса часто работают в разы быстрее одного сложного.

Резюме

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

← На главную