В практике разработки на платформе 1С встречаются ситуации, когда использование стандартных виртуальных таблиц (Остатки, Обороты, ОстаткиИОбороты) становится невозможным. Чаще всего это происходит из-за повреждения итоговых таблиц базы данных или специфических требований к расчету нарастающих итогов "на каждый документ" в рамках одного запроса. Работа с физическими таблицами напрямую требует от программиста глубокого понимания механизмов индексации СУБД и особенностей компоновки запросов.
Разберем детально, как можно оптимизировать тяжелый запрос к регистру Хозрасчетный, который собирает данные по списанию материальных запасов, используя физические таблицы проводок и субконто.
Проанализируем исходную ситуацию. Основная нагрузка в таких запросах ложится на несколько узких мест:
СчетДт В (...) ИЛИ СчетКт В (...) заставляет СУБД выполнять полное сканирование индекса или всей таблицы, так как она не может эффективно применить индекс сразу к двум разным полям.РегистрБухгалтерии.Хозрасчетный.Субконто содержит огромное количество записей (кратное количеству проводок и видов субконто). Соединение с ней по Регистратору — одна из самых дорогих операций.Т1.МоментВремени > Т2.МоментВремени имеет квадратичную сложность O(n²). Если в выборке 1000 записей, СУБД придется выполнить до 1 000 000 сравнений.ПОМЕСТИТЬ, последующие соединения (JOIN) будут выполняться крайне медленно, особенно на больших объемах данных.Вместо использования условия ИЛИ в предложении ГДЕ, которое блокирует эффективное использование индексов, мы применим объединение двух независимых выборок. Это позволит СУБД использовать индекс по СчетДт для первой части и индекс по СчетКт для второй.
Рассмотрим, как переписать этот блок:
ВЫБРАТЬ
Хозрасчетный.СчетДт КАК Счет,
Хозрасчетный.Сумма КАК Сумма,
Хозрасчетный.КоличествоДт КАК Количество,
Хозрасчетный.Регистратор,
Хозрасчетный.НомерСтроки,
Хозрасчетный.Период,
Хозрасчетный.МоментВремени,
"Дт" КАК ВидДвижения
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
ГДЕ
Хозрасчетный.Активность
И Хозрасчетный.Период <= &ПериодКонец
И Хозрасчетный.СчетДт В (ВЫБРАТЬ Счет ИЗ втСпСчетов)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Хозрасчетный.СчетКт,
Хозрасчетный.Сумма,
Хозрасчетный.КоличествоКт,
Хозрасчетный.Регистратор,
Хозрасчетный.НомерСтроки,
Хозрасчетный.Период,
Хозрасчетный.МоментВремени,
"Кт"
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
ГДЕ
Хозрасчетный.Активность
И Хозрасчетный.Период <= &ПериодКонец
И Хозрасчетный.СчетКт В (ВЫБРАТЬ Счет ИЗ втСпСчетов)
Такой подход гарантирует "попадание в индекс" (Index Seek), что критически важно для производительности на серверах под управлением PostgreSQL или MS SQL Server.
Это самый простой и эффективный способ ускорить выполнение запроса. Выясним причину: когда мы помещаем данные во временную таблицу втЗапСчетов, платформа 1С создает реальную таблицу в базе данных СУБД. Без индексов любое соединение с этой таблицей будет приводить к Table Scan.
Обязательно индексируем поля, которые участвуют в соединениях (JOIN) и условиях СГРУППИРОВАТЬ ПО. В нашем случае это Регистратор, НомерСтроки, Счет и поля субконто.
ВЫБРАТЬ ...
ПОМЕСТИТЬ втОбЗаПериод
ИЗ ...
ИНДЕКСИРОВАТЬ ПО
Счет,
Субконто1,
Субконто2,
МоментВремени
;
Как показала практика обсуждения, добавление индексов позволило сократить время выполнения запроса с неопределенного времени до приемлемых 30 секунд за квартал.
Обращение к таблице РегистрБухгалтерии.Хозрасчетный.Субконто — очень "дорогая" операция. Чтобы минимизировать нагрузку, проанализируем последовательность действий. Не стоит выбирать все субконто для всех проводок. Сначала максимально ограничим выборку проводок по счетам и периоду, поместим их во временную таблицу, и только потом присоединим субконто.
Также важно помнить, что в физической таблице субконто поле Вид является частью индекса. Поэтому условие по виду субконто (Номенклатура, Склады) должно быть максимально простым и точным.
Самая сложная часть запроса — расчет остатка на момент каждого документа. Соединение втОбЗаПериод КАК втОбЗаПериод1 с самой собой по условию МоментВремени > МоментВремени1 — это классический "убийца производительности".
Проанализируем альтернативные варианты:
ТаблицаЗначений, отсортировать ее и рассчитать остатки простым циклом Для Каждого. Процессор сервера приложений справится с этим за миллисекунды, в то время как СУБД может "задохнуться" на вложенных циклах.Пример оптимизированного соединения для нарастающего итога с предварительной группировкой:
ВЫБРАТЬ
Т1.Счет,
Т1.Субконто1,
Т1.Субконто2,
Т1.Регистратор,
СУММА(Т2.Количество) КАК НарастающийОстаток
ИЗ
втОбороты КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОбороты КАК Т2
ПО Т1.Счет = Т2.Счет
И Т1.Субконто1 = Т2.Субконто1
И Т1.Субконто2 = Т2.Субконто2
И Т1.МоментВремени >= Т2.МоментВремени
СГРУППИРОВАТЬ ПО
Т1.Счет,
Т1.Субконто1,
Т1.Субконто2,
Т1.Регистратор
Если ваша база работает на PostgreSQL, помните о важности статистики. При интенсивном использовании временных таблиц планировщик запросов может ошибаться, считая, что в таблице 0 или 1 запись. Это приводит к выбору неоптимального плана (например, постоянному использованию Nested Loops там, где нужен Hash Join).
Посмотрим на рекомендации для этой СУБД:
- Используйте ВЫРАЗИТЬ для приведения типов в соединениях, чтобы избежать неявного преобразования типов (Implicit Cast), которое не позволяет использовать индексы.
- Разделяйте один огромный запрос на несколько вызовов метода Запрос.Выполнить(). Это позволит СУБД актуализировать статистику между этапами (хотя в рамках одного пакета 1С это не всегда эффективно, разделение на разные пакеты в коде помогает).
Рассмотрев проблему, мы выяснили, что "битые" итоги в базе данных — это серьезная техническая проблема, которую лучше всего решать штатным средством: Тестирование и исправление -> Пересчет итогов. Однако, если вам крайне необходимо получить данные из физических таблиц, соблюдайте следующие правила:
ОБЪЕДИНИТЬ ВСЕ.Субконто.Период + Регистратор для правильной сортировки движений внутри одной секунды.Следуя этим шагам, вы сможете добиться стабильной работы системы даже в тех случаях, когда стандартные механизмы 1С временно недоступны или требуют дополнительной проверки.