Разберем распространенную ситуацию: в конфигурации требуется отслеживать местоположение или состояние объектов (например, вагонов, оборудования, сотрудников) во времени. Начинающие разработчики нередко создают для этих целей два справочника — один для списка объектов, а второй для хранения истории их изменений, после чего связывают их запросом с группировкой по максимальной дате. Выясним, почему такой подход приводит к критическому падению производительности и как спроектировать систему по стандартам платформы 1С.
Рассмотрим типовой запрос, в котором из справочника истории сначала вычисляется максимальная дата для каждого объекта, а затем повторным соединением отбираются детальные данные:
ВЫБРАТЬ
Список.Код КАК Номер,
Список.ФлагРемонт КАК Ремонт,
Список.КонтрагентАналитика КАК Аналитика,
Список.ТипПользования КАК Пользование,
МАКСИМУМ(Дислокация.ДатаПолучения) КАК МаксДата
ПОМЕСТИТЬ ВТ_ДислокацияАрхив
ИЗ
Справочник.ДислокацияВагоныСписок КАК Список
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДислокацияВагоныАрхив КАК Дислокация
ПО Список.Код = Дислокация.Номер
СГРУППИРОВАТЬ ПО
Список.Код,
Список.ФлагРемонт,
Список.КонтрагентАналитика,
Список.ТипПользования
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Дислокация2.Номер,
Дислокация2.Тип,
Дислокация2.Статус,
Дислокация2.СтанцияНазначения,
Дислокация2.Оператор,
Дислокация2.Расстояние КАК Раст,
ВТ_ДислокацияАрхив.Ремонт,
ВТ_ДислокацияАрхив.Аналитика,
ВТ_ДислокацияАрхив.Пользование КАК Польз
ИЗ
Справочник.ДислокацияВагоныАрхив КАК Дислокация2
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДислокацияАрхив КАК ВТ_ДислокацияАрхив
ПО Дислокация2.Номер = ВТ_ДислокацияАрхив.Номер
И Дислокация2.ДатаПолучения = ВТ_ДислокацияАрхив.МаксДата
При тестировании такого запроса часто наблюдается эффект: первый запуск после добавления данных занимает длительное время (от десятков секунд до минут), а последующие повторные выполнения происходят практически мгновенно.
Проанализируем причину такого поведения. Быстрое повторное выполнение связано с кэшированием данных в оперативной памяти (как на уровне СУБД, так и на уровне платформы 1С). При первом выполнении данные считываются с диска в буферный кэш. Пока исходные таблицы не модифицируются, результат или страницы данных берутся напрямую из памяти. Очистка временных файлов пользователя на клиентском компьютере не сбрасывает кэш страниц СУБД на сервере, поэтому запрос продолжает выполняться быстро до перезапуска службы или модификации данных.
Посмотрим, какие технические проблемы заложены в исходной схеме запроса:
ВТ_ДислокацияАрхив не указано предложение ИНДЕКСИРОВАТЬ ПО Номер, МаксДата. Во втором пакете соединение идет по двум неиндексированным полям, что вынуждает СУБД выполнять полное сканирование (Table Scan / Nested Loops) без использования индексов.ФлагРемонт, КонтрагентАналитика, ТипПользования). Это многократно увеличивает объем промежуточных вычислений. Достаточно было сгруппировать только ключевые поля и дату, а дополнительные реквизиты присоединять в финальном пакете.ДатаПолучения), соединение ПО Номер И МаксДата вернет дубли строк в итоговой выборке.Номер, а не по системной ссылке Ссылка, что исключает эффективное использование кластерных индексов.Хранение истории изменения состояний объектов в справочниках является грубой архитектурной ошибкой. В платформе 1С для этих целей предназначен объект метаданных Регистр сведений с установленной периодичностью (например, По позиции регистратора или В пределах секунды).
Разберем шаги по правильной организации структуры:
ДислокацияВагонов) с периодичностью По позиции регистратора или В пределах секунды.Вагон (тип СправочникСсылка.ДислокацияВагоныСписок) и установим для него свойство Ведущее. Это заставит СУБД создать оптимальный индекс по сочетанию [Измерение + Период].СтанцияНазначения, Статус, Оператор, Расстояние, Тип.Теперь получение актуального среза на любую дату выполняется в одну строку через виртуальную таблицу СрезПоследних:
ВЫБРАТЬ
Список.Код КАК Номер,
Список.ФлагРемонт КАК Ремонт,
Список.КонтрагентАналитика КАК Аналитика,
Список.ТипПользования КАК Пользование,
Срез.Статус,
Срез.СтанцияНазначения,
Срез.Оператор,
Срез.Расстояние КАК Раст,
Срез.Тип
ИЗ
Справочник.ДислокацияВагоныСписок КАК Список
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДислокацияВагонов.СрезПоследних(&Период, ) КАК Срез
ПО Список.Ссылка = Срез.Вагон
Важное правило работы с виртуальными таблицами: если требуется отобрать срез только по определенному подмножеству вагонов, условия обязательно нужно передавать в первый или второй параметр виртуальной таблицы СрезПоследних(&Период, Вагон В (&СписокВагонов)), а не в секцию ГДЕ. Это позволит платформе сразу ограничить выборку на уровне СУБД, не сканируя весь регистр.
Если система высоконагруженная, а количество записей истории исчисляется миллионами, вычисление среза последних даже по периодическому регистру может требовать ресурсов СУБД при частых обращениях. В таких сценариях применяют паттерн раздельного хранения:
Вагон; Ресурсы: АктуальнаяДата, Станция, Статус и т.д.). При записи новой координаты в историю значение в непериодическом регистре просто перезаписывается.Запрос к актуальным данным из непериодического регистра обращается к физической таблице без агрегатных функций МАКСИМУМ и без использования подзапросов, обеспечивая минимальное время отклика даже в файловых базах данных.