В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой производительности при получении данных из виртуальных таблиц информационных регистров, особенно когда речь идет о функции СрезПоследних. При росте объема записей в регистре (сотни тысяч и миллионы строк) стандартное обращение к виртуальной таблице может занимать недопустимо много времени. Проанализируем, почему это происходит, и разберем эффективные методы решения этой задачи.
Для начала выясним причину, по которой стандартный СрезПоследних может работать медленно. На уровне СУБД запрос к виртуальной таблице превращается в сложную конструкцию с подзапросами или использованием соединений для поиска максимальной даты (периода) для каждой комбинации измерений. Если мы накладываем условия на измерения внутри параметров виртуальной таблицы, 1С старается оптимизировать запрос, но при использовании сложных условий или соединений с другими таблицами эффективность падает.
Рассмотрим ситуацию, когда нам нужно получить актуальные цены номенклатуры из большого списка. Если просто соединить таблицу товаров с виртуальной таблицей ЦеныНоменклатуры.СрезПоследних, система может выполнить расчет среза для всех товаров в базе, а уже потом отсечь нужные. Это критическая ошибка проектирования.
Проанализируем наиболее надежный способ ускорения — предварительный отбор ключей связи во временную таблицу. Это позволяет ограничить выборку из регистра только теми записями, которые нам действительно нужны. Разберем этот процесс по шагам.
Сначала мы подготовим список объектов, по которым требуется получить данные, и поместим их в МенеджерВременныхТаблиц. Это позволит индексу СУБД эффективно отработать при соединении с физической таблицей регистра.
Посмотрим на пример кода, реализующего этот подход:
// Шаг 1: Подготовка целевого списка номенклатуры
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СписокТоваров.Ссылка КАК Номенклатура
|ПОМЕСТИТЬ ВТ_СписокТоваров
|ИЗ
| &ТаблицаТоваров КАК СписокТоваров
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Шаг 2: Получение данных из среза последних с фильтром по временной таблице
|ВЫБРАТЬ
| Цены.Номенклатура,
| Цены.Цена,
| Цены.Валюта
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_СписокТоваров)) КАК Цены";
Запрос.УстановитьПараметр("ТаблицаТоваров", ИсходныеДанные);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Результат = Запрос.Выполнить();
В данном примере использование конструкции Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_СписокТоваров) внутри параметров виртуальной таблицы позволяет платформе передать фильтр непосредственно в SQL-запрос. Это критически важно для производительности.
В некоторых случаях, когда виртуальная таблица продолжает работать медленно (например, из-за особенностей оптимизатора конкретной версии СУБД), мы можем прибегнуть к "ручному" расчету среза последних через обращение к основной физической таблице регистра. Рассмотрим, как это сделать правильно.
Этот метод заключается в получении максимальных дат для каждого измерения и последующем соединении с основной таблицей регистра для получения ресурсов. Проанализируем шаги реализации:
Разберем пример кода для этого метода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Регистр.Номенклатура КАК Номенклатура,
| МАКСИМУМ(Регистр.Период) КАК МаксПериод
|ПОМЕСТИТЬ ВТ_МаксимальныеДаты
|ИЗ
| РегистрСведений.ЦеныНоменклатуры КАК Регистр
|ГДЕ
| Регистр.Период <= &Период
| И Регистр.Номенклатура В (&СписокНоменклатуры)
|ГРУППИРОВАТЬ ПО
| Регистр.Номенклатура
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура,
| МаксПериод
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Регистр.Номенклатура,
| Регистр.Цена
|ИЗ
| ВТ_МаксимальныеДаты КАК Даты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Регистр
| ПО Даты.Номенклатура = Регистр.Номенклатура
| И Даты.МаксПериод = Регистр.Период";
Запрос.УстановитьПараметр("Период", НужнаяДата);
Запрос.УстановитьПараметр("СписокНоменклатуры", МассивТоваров);
Результат = Запрос.Выполнить();
Обратите внимание на использование ИНДЕКСИРОВАТЬ ПО. Без создания индекса во временной таблице соединение с большой физической таблицей регистра будет крайне медленным, так как СУБД придется выполнять сканирование (Scan) вместо поиска (Seek).
Посмотрим на ситуацию со стороны метаданных. Эффективность любого запроса к регистру сведений зависит от того, как расставлены индексы. В 1С для периодических регистров сведений платформа автоматически создает индекс по комбинации Период + Измерения. Однако порядок измерений в конфигураторе имеет значение.
Важный момент: наиболее селективные измерения (те, которые максимально ограничивают выборку) должны стоять первыми в списке измерений регистра. Это позволит механизму Index Seek работать быстрее.
Рассмотрим дополнительные рекомендации по оптимизации:
Основной отбор по периоду, обязательно используйте его в условиях запроса.ГДЕ после выбора из виртуальной таблицы СрезПоследних. Все фильтры должны передаваться в параметры виртуальной таблицы.Проанализируем преимущество пакетных запросов при работе с МенеджерВременныхТаблиц. Когда мы выполняем серию операций в одном объекте Запрос, 1С удерживает временные таблицы в базе данных (в tempdb СУБД), что исключает лишние накладные расходы на передачу данных между сервером приложений и сервером баз данных.
Рассмотрим пример сложной оптимизации, когда нужно получить срез последних сразу по нескольким связанным регистрам:
ТекстЗапроса =
"ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВТ_Объекты ИЗ Справочник.Номенклатура ГДЕ ... ИНДЕКСИРОВАТЬ ПО Ссылка;
|ВЫБРАТЬ Срез.Номенклатура, Срез.Цена ПОМЕСТИТЬ ВТ_Цены ИЗ РегистрСведений.Цены.СрезПоследних(&П, Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВТ_Объекты)) КАК Срез;
|ВЫБРАТЬ Срез.Номенклатура, Срез.Остаток ПОМЕСТИТЬ ВТ_Остатки ИЗ РегистрНакопления.Запасы.Остатки(&П, Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВТ_Объекты)) КАК Срез;
|ВЫБРАТЬ ВТ_Объекты.Ссылка, ВТ_Цены.Цена, ВТ_Остатки.Остаток ИЗ ВТ_Объекты
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены ПО ВТ_Объекты.Ссылка = ВТ_Цены.Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки ПО ВТ_Объекты.Ссылка = ВТ_Остатки.Номенклатура";
В данном сценарии мы сначала жестко ограничиваем набор данных во временной таблице ВТ_Объекты, а затем используем её как "фильтр" для всех последующих виртуальных таблиц. Это классический паттерн высокопроизводительной разработки на платформе 1С.
Подведем итог нашему анализу. Оптимизация получения среза последних сводится к трем основным правилам:
Выясним, какой метод выбрать. Если вы работаете с типовыми конфигурациями и стандартными механизмами, Метод 1 (фильтр в параметрах) является предпочтительным и наиболее читаемым. Если же вы сталкиваетесь с эксклюзивными случаями деградации производительности на специфических версиях SQL-серверов, Метод 2 (ручной расчет через физическую таблицу) станет вашим "спасательным кругом".
Помните, что каждый запрос стоит проверять в Консоли запросов с включенным замером времени, а в идеале — анализировать план запроса в SQL Server Management Studio для понимания того, как именно СУБД обрабатывает ваш код 1С.