При работе с высоконагруженными системами на платформе 1С:Предприятие 8 программисты часто сталкиваются с тем, что стандартная виртуальная таблица СрезПоследних начинает работать крайне медленно. В регистрах с количеством записей более миллиона выполнение простого запроса может занимать 15–40 секунд, что недопустимо для оперативной работы пользователя. В этой статье мы подробно разберем, почему это происходит на уровне СУБД, и рассмотрим проверенные методы оптимизации, которые позволяют сократить время выполнения запроса с десятков секунд до сотых долей секунды.
Для начала проанализируем, почему стандартный запрос к СрезПоследних может тормозить. Рассмотрим типичный код:
ВЫБРАТЬ
Цены.ТипЦен,
Цены.Номенклатура,
Цены.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ТипЦен = &ТипЦен И Номенклатура = &Номенклатура) КАК Цены
Когда платформа 1С транслирует этот запрос в язык SQL, она создает сложную конструкцию с вложенными подзапросами. На уровне СУБД это выглядит примерно так: сначала система ищет максимальную дату (MAX(Period)) для каждой комбинации измерений, входящих в фильтр, а затем выполняет соединение (INNER JOIN) полученного набора с основной физической таблицей регистра, чтобы достать ресурсы (в нашем примере — поле Цена).
Проблема заключается в том, что оптимизатор SQL Server не всегда может эффективно пробросить индексы внутрь этих вложенных конструкций. Вместо быстрого поиска по индексу (Index Seek), сервер может выбрать полное сканирование таблицы (Index Scan) или ресурсозатратное соединение типа Merge Join. Ситуация усугубляется, если статистика базы данных не актуальна или план запроса был закеширован для других параметров.
Выясним, как обойти ограничения платформенного механизма. Самый эффективный способ — "разрезать" запрос на два этапа с помощью временной таблицы. Мы самостоятельно выполним поиск максимального периода, а затем соединим его с физической таблицей регистра.
Рассмотрим алгоритм реализации этого метода:
ГДЕ.Проанализируем пример оптимизированного запроса:
// Шаг 1: Получаем последние даты по нужным измерениям
ВЫБРАТЬ
Цены.ТипЦен,
Цены.Номенклатура,
МАКСИМУМ(Цены.Период) КАК Период
ПОМЕСТИТЬ ВТ_Периоды
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
ГДЕ
Цены.Период <= &Период
И Цены.ТипЦен = &ТипЦен
И Цены.Номенклатура = &Номенклатура
СГРУППИРОВАТЬ ПО
Цены.ТипЦен,
Цены.Номенклатура;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем цену, соединяясь с физической таблицей
ВЫБРАТЬ
ВТ_Периоды.ТипЦен,
ВТ_Периоды.Номенклатура,
ЦеныРеальные.Цена
ИЗ
ВТ_Периоды КАК ВТ_Периоды
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныРеальные
ПО ВТ_Периоды.ТипЦен = ЦеныРеальные.ТипЦен
И ВТ_Периоды.Номенклатура = ЦеныРеальные.Номенклатура
И ВТ_Периоды.Период = ЦеныРеальные.Период
Такой подход работает быстрее по нескольким причинам. Во-первых, при работе с временной таблицей SQL Server может построить более точный план выполнения. Во-вторых, первый запрос пакета работает только с индексированными полями (измерения + период), что позволяет СУБД мгновенно найти нужные записи без обращения к данным ресурсов.
Рассмотрим интересный нюанс: часто бывает так, что запрос, выбирающий только измерения, работает мгновенно, но как только мы добавляем в выборку поле Цена (ресурс), начинаются тормоза. Это связано с операцией Key Lookup (или RID Lookup).
Посмотрим на ситуацию: индекс регистра обычно содержит только Период и Измерения. Значения ресурсов (цены, остатки) хранятся в основной таблице. Когда SQL находит подходящую запись в индексе, ему нужно "сбегать" в основную таблицу за ценой. Если таких записей много, сервер может решить, что проще просканировать всю таблицу целиком, чем делать тысячи мелких переходов.
Для оптимизации в этом случае стоит проверить:
Проанализируем, куда именно мы помещаем условия отбора. В 1С есть два способа ограничить выборку из виртуальной таблицы:
Регистр.СрезПоследних(&Период, Номенклатура = &Номенклатура).ГДЕ Цены.Номенклатура = &Номенклатура.Важно: Платформа 1С всегда рекомендует использовать первый вариант. Это позволяет системе передать фильтры в самый глубокий вложенный запрос SQL. Если же фильтры стоят в ГДЕ, СУБД сначала вычислит срез последних для всех товаров в базе, а уже потом отсечет лишние, что приведет к катастрофическому падению производительности.
Однако в старых версиях платформы (до 8.3.12) даже правильные параметры не всегда спасали. Если вы столкнулись с тем, что стандартная фильтрация не помогает, попробуйте передать отбор через конструкцию В (ВЫБРАТЬ...):
ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, (ТипЦен, Номенклатура) В (ВЫБРАТЬ &ТипЦен, &Номенклатура))
Иногда это "подсказывает" оптимизатору SQL правильный путь выполнения запроса.
Если вы используете платформу версии 8.3.12 и выше, вы можете включить специальную опцию в свойствах регистра сведений — "Разрешить итоги: Срез последних".
Разберем, как это меняет работу системы:
СрезПоследних (без указания даты, на текущий момент) превращается в простую выборку из этой готовой таблицы.Рассмотрим ограничение: этот механизм работает только для получения среза на "сейчас". Если вам нужно получить данные на прошлую дату (например, вчерашнюю цену), система снова будет использовать медленный механизм вычисления через MAX(Period).
Подведем итог нашему исследованию. Если ваш СрезПоследних начал тормозить, выполните следующие шаги:
Применяя эти методы, мы обеспечиваем стабильную и быструю работу конфигурации даже при многомиллионных оборотах данных. Помните, что каждый запрос к виртуальной таблице — это потенциальное узкое место, которое требует внимательного анализа плана выполнения на уровне СУБД.