Как хранятся итоги и срезы в 1С 8 и почему отборы нужно передавать в параметры виртуальных таблиц?

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

При переходе с версии 1С 7.7 на платформу 1С:Предприятие 8 у многих разработчиков возникает вопрос: как устроено хранение актуальных итогов регистров и срезов последних, и почему стандарты разработки настоятельно требуют передавать все условия фильтрации исключительно в параметры виртуальных таблиц, а не в секцию ГДЕ или через соединения. Разберем детально физическую структуру хранения данных в СУБД, логику работы платформы и влияние условий на производительность запросов.

Физическое хранение итогов регистров накопления и сведений в СУБД

Проанализируем, как платформа 1С организует таблицы на уровне реляционной СУБД:

  1. Регистры накопления (остатки): В отличие от 1С 7.7, в платформе 8 нет отдельной таблицы для «точки актуальности». Все рассчитанные итоги (помесячные срезы и финальное сальдо) хранятся в единой физической таблице итогов регистра с префиксом AccumRgT<n>. Записи с максимально возможной датой (31.12.3999 или 01.01.4000 в зависимости от СУБД) представляют собой общее итоговое сальдо со всеми когда-либо проведенными движениями. Когда мы обращаемся к виртуальной таблице остатков без указания даты, платформа читает именно эти строки на 3999 год без обращения к таблице движений AccumRg<n>.
  2. Регистры сведений: Для виртуальных таблиц СрезПоследних и СрезПервых отдельных физических таблиц в базе данных вообще не существует. Данные среза всегда рассчитываются динамически «на лету» на основе основной физической таблицы регистра сведений InfoRg<n> через подзапрос с поиском максимальной даты для каждого набора измерений.

Почему фильтрация в секции ГДЕ приводит к падению производительности

Выясним, почему обращение к виртуальной таблице даже за актуальными итогами требует передачи отборов внутрь её параметров. Дело в том, как платформа 1С транслирует виртуальные таблицы в SQL-код для СУБД:

  1. Группировка и разделение итогов: При любом запросе к виртуальной таблице остатков платформа формирует внутренний подзапрос с агрегатными функциями SUM и секцией GROUP BY. Это необходимо, так как включенный механизм разделения итогов создает несколько физических строк с системным полем _Splitter для одной и той же комбинации измерений.
  2. Проталкивание предиката (Predicate Pushdown):
    • Если мы передаем отбор в параметры виртуальной таблицы, платформа помещает условие фильтрации внутрь самого подзапроса. СУБД применяет индексный поиск (Index Seek) и отбирает только нужные записи до выполнения группировки и суммирования.
    • Если отбор помещен в общее условие ГДЕ или задается через соединение JOIN, оптимизаторы СУБД (MS SQL Server, PostgreSQL) часто не могут протолкнуть предикат внутрь агрегирующего подзапроса. В этом случае СУБД сначала сканирует и группирует все строки таблицы итогов базы данных в памяти или во временных таблицах (tempdb), и лишь затем отсекает ненужное, вызывая колоссальную нагрузку на дисковую подсистему и процессор.
  3. Особенности среза последних: Если в СрезПоследних не передать фильтр по измерениям, подзапрос СУБД будет вынужден сначала найти максимальные даты для всех существующих в регистре объектов, выполнив полное сканирование индекса (Index Scan), а затем отфильтровать одну нужную строку.

Правильные и неправильные паттерны построения запросов

Рассмотрим практические примеры и сравним подходы к написанию запросов.

Неэффективный вариант: условие наложено в секции ГДЕ или через левое соединение с другой таблицей без параметров в виртуальной таблице:


// НЕ РЕКОМЕНДУЕТСЯ: СУБД может сначала агрегировать все остатки на 3999 год
ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки() КАК Остатки
ГДЕ
    Остатки.Номенклатура = &Номенклатура

Рекомендуемый вариант: отбор передан непосредственно в параметры виртуальной таблицы:


// ПРАВИЛЬНО: фильтрация происходит на этапе индексного чтения из таблицы итогов
ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК Остатки

Как передавать условия из временных таблиц

Разберем ситуацию, когда требуется получить остатки или срез сведений не по одиночному значению, а по списку строк из другой таблицы (например, из табличной части документа или временной таблицы). Часто разработчики совершают ошибку, связывая временную таблицу с виртуальной через ВНУТРЕННЕЕ СОЕДИНЕНИЕ без параметров. Корректный способ заключается в передаче подзапроса в параметры виртуальной таблицы через оператор В:


// 1. Формируем временную таблицу с требуемыми товарами
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ДокТовары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК ДокТовары
ГДЕ
    ДокТовары.Ссылка = &Ссылка;

ИНДЕКСИРОВАТЬ ПО
    Номенклатура;
;

// 2. Получаем остатки, передавая отбор во внутренний параметр
ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.Склад КАК Склад,
    Остатки.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        ,
        Номенклатура В (ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТ_Товары КАК ВТ)
    ) КАК Остатки

Сводные правила оптимизации работы с итогами

  1. Всегда указываем фильтры по измерениям в параметрах виртуальных таблиц остатков, оборотов и срезов.
  2. Порядок полей в условии фильтрации должен по возможности повторять порядок измерений регистра, чтобы гарантировать попадание в кластерный индекс.
  3. Следим за регулярным выполнением регламентного задания пересчета итогов: если рассчитанные итоги сильно отстают от текущей даты, запросы на исторические даты будут выполнять тяжелое чтение таблицы движений.
← На главную