В процессе разработки отчетов в 1С:Розница 2.3 программисты часто сталкиваются с необъяснимым, на первый взгляд, поведением системы: один и тот же запрос за разные периоды выполняется с колоссальной разницей во времени. Ситуация, когда расчет за декабрь занимает минуту, а за январь — пятнадцать, при этом повторный запуск "лечит" проблему, является классическим симптомом для файловых баз данных. Рассмотрим подробно, почему это происходит и как оптимизировать код запроса.
Проанализируем ситуацию, когда после перезагрузки базы или при первом обращении к данным за новый период запрос "зависает". Основная причина здесь кроется в механизмах кэширования операционной системы и самой платформы 1С. Файловая СУБД 1С (файл .1CD) очень сильно зависит от дисковых операций чтения. При первом выполнении запроса ("холодный старт") системе необходимо физически вычитать данные с диска, просканировать индексы и загрузить их в оперативную память (системный кэш Windows).
Когда вы выполняете запрос за Декабрь, необходимые страницы данных и индексов из таблицы итогов регистра ТоварыНаСкладах попадают в память. Если данные за Январь находятся в тех же или соседних блоках данных, последующий запрос за Январь выполняется уже как "горячий", используя данные из оперативной памяти, что в десятки раз быстрее.
Также стоит учитывать влияние антивирусного ПО. При активном обращении к большому файлу базы данных антивирус может начать его сканирование "на лету", что создает дополнительную нагрузку на дисковую подсистему и замедляет чтение данных в разы.
Выясним причину, по которой выбор периода (начало или конец месяца) критически влияет на скорость. В 1С:Предприятие итоги хранятся в специальных таблицах на начало каждого месяца (00:00:00 первого числа). Рассмотрим, как работает виртуальная таблица Остатки:
01.01.2021 00:00:00, платформа просто берет готовые цифры из таблицы итогов. Это происходит мгновенно.31.01.2021 23:59:59, платформе необходимо взять итоги на 01.02.2021 и вычесть из них все движения, совершенные за январь, либо взять итоги на 01.01.2021 и прибавить движения января. В любом случае, происходит сканирование основной таблицы движений регистра, что при большом количестве документов занимает много времени.Важный совет: Если вам нужны остатки "на конец месяца", всегда старайтесь использовать границу начала следующего месяца (первое число, 00:00:00), чтобы система могла обратиться к рассчитанным итогам напрямую.
Посмотрим на исходный запрос, который был представлен автором темы. В нем содержится несколько критических ошибок, замедляющих работу в файловом режиме.
ВЫБРАТЬ
Номенклатура.Родитель КАК Родитель,
СУММА(1) КАК Всего,
Номенклатура.Ссылка КАК Ссылка
ПОМЕСТИТЬ Ном
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
СГРУППИРОВАТЬ ПО
Номенклатура.Родитель,
Номенклатура.Ссылка
В этой части запроса мы видим избыточную группировку. Группировать по Ссылка не имеет смысла, так как ссылка уникальна. Это заставляет систему тратить ресурсы на сортировку и свертку там, где это не требуется. Кроме того, создание временной таблицы Ном, содержащей фактически весь справочник, создает лишнюю нагрузку на память.
Рассмотрим вторую часть — получение остатков:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура.Ссылка КАК НоменклатураСсылка,
ТоварыНаСкладахОстатки.Номенклатура.Родитель КАК НоменклатураРодитель,
СУММА(1) КАК ВНаличии
ПОМЕСТИТЬ Ост
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Номенклатура.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК ТоварыНаСкладахОстатки
СГРУППИРОВАТЬ ПО
ТоварыНаСкладахОстатки.Номенклатура.Родитель,
ТоварыНаСкладахОстатки.Номенклатура.Ссылка
Здесь допущена главная ошибка производительности — обращение через точку в параметрах виртуальной таблицы. Условие Номенклатура.Родитель <> ... внутри скобок Остатки() заставляет 1С выполнять неявное соединение таблиц регистра и справочника для каждой записи в процессе расчета итогов. В файловом режиме это превращает запрос в крайне медленный процесс.
Разберем по шагам, как переписать этот запрос правильно, чтобы он "летал" даже в файловой базе. Нам нужно минимизировать количество обрабатываемых данных и исключить неявные соединения.
Проанализируем оптимизированный вариант запроса:
// Шаг 1: Получаем остатки без лишних условий "через точку"
ВЫБРАТЬ
ТоварыОстатки.Номенклатура КАК Номенклатура,
ТоварыОстатки.КоличествоОстаток КАК Количество
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК ТоварыОстатки
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
// Шаг 2: Формируем итоговый результат
ВЫБРАТЬ
Номенклатура.Родитель КАК Родитель,
СУММА(ВЫБОР КОГДА ВТ_Остатки.Количество ЕСТЬ NULL ТОГДА 0 ИНАЧЕ 1 КОНЕЦ) КАК ВНаличии,
СУММА(1) КАК Всего
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
ПО Номенклатура.Ссылка = ВТ_Остатки.Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
И Номенклатура.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
СГРУППИРОВАТЬ ПО
Номенклатура.Родитель
В данном примере мы исключили ресурсоемкие операции внутри виртуальной таблицы. Использование ИНДЕКСИРОВАТЬ ПО для временной таблицы ВТ_Остатки позволяет ЛЕВОМУ СОЕДИНЕНИЮ отработать максимально быстро. Также обратите внимание, что мы проверяем условие НЕ Номенклатура.ЭтоГруппа, чтобы считать только элементы, а не сами папки справочника.
Помимо оптимизации кода, не забывайте о регламентных операциях, которые напрямую влияют на скорость запросов к итогам:
.1CD — раздуваться после удаления данных.Таким образом, мы выяснили, что "магия" ускорения запроса после повторного запуска — это всего лишь работа дискового кэша, а длительное выполнение — следствие неоптимальных фильтров в параметрах виртуальных таблиц и специфики хранения итогов в 1С.