Одной из наиболее распространенных проблем производительности в системе 1С:Предприятие 8.3 является медленная работа запросов, в которых используются виртуальные таблицы (например, Остатки, Обороты или СрезПоследних). Часто программисты сталкиваются с тем, что отчет, который быстро работал на тестовой базе, начинает «зависать» на реальных данных. В этой статье мы подробно разберем, почему это происходит, проанализируем внутренние механизмы работы СУБД и научимся правильно оптимизировать такие запросы.
Разберем ситуацию, когда в одном запросе соединяются две виртуальные таблицы, например, остатки товаров и цены из регистра сведений. На уровне СУБД виртуальная таблица не существует в виде физической таблицы. Это сложный вложенный запрос, который система 1С генерирует «на лету» при обращении к объекту. Если мы соединяем две такие таблицы напрямую через ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ, оптимизатор SQL-сервера часто не может построить эффективный план выполнения. Это приводит к так называемому «вложенному циклу» (Nested Loops) или полному сканированию таблиц, что катастрофически замедляет выполнение при росте объема данных.
Первое правило оптимизации: фильтрация данных должна происходить как можно раньше. Рассмотрим типичную ошибку, когда условие ГДЕ накладывается на результат выполнения запроса, а не передается внутрь виртуальной таблицы. Проанализируем пример кода:
// Неправильно: фильтрация после получения всех данных
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Период) КАК Остатки
ГДЕ
Остатки.Склад = &Склад
В данном случае система сначала получит остатки по всем складам из базы данных, и только потом отсечет ненужные. Чтобы ускорить работу, мы должны использовать параметры виртуальной таблицы. Рассмотрим правильный вариант:
// Правильно: использование параметров виртуальной таблицы
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК Остатки
В этом случае в СУБД уйдет запрос, который сразу выберет данные только по конкретному складу, что значительно снизит нагрузку на дисковую подсистему и оперативную память.
Если нам необходимо соединить данные из нескольких источников, самым надежным способом является использование механизма временных таблиц. Разберем по шагам, как это реализовать. Вместо того чтобы соединять виртуальную таблицу Остатки с таблицей СрезПоследних, мы сначала поместим результаты каждого запроса в отдельную временную таблицу.
Проанализируем преимущества этого подхода:
Посмотрим на пример реализации такого подхода в коде 1С:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура В (&СписокНоменклатуры)) КАК ТоварыНаСкладахОстатки
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура В (ВЫБРАТЬ ВТ_Остатки.Номенклатура ИЗ ВТ_Остатки)) КАК ЦеныНоменклатурыСрезПоследних
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Остатки.Номенклатура,
| ВТ_Остатки.Количество,
| ЕСТЬNULL(ВТ_Цены.Цена, 0) КАК Цена,
| ВТ_Остатки.Количество * ЕСТЬNULL(ВТ_Цены.Цена, 0) КАК Сумма
|ИЗ
| ВТ_Остатки КАК ВТ_Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены КАК ВТ_Цены
| ПО ВТ_Остатки.Номенклатура = ВТ_Цены.Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Обратите внимание на ключевое слово ИНДЕКСИРОВАТЬ ПО в примере выше. Без индексации соединение даже временных таблиц может быть медленным, если в них содержится несколько тысяч строк. Мы рекомендуем всегда индексировать поля, по которым происходит связь (ПО) или условие (ГДЕ) в последующих частях запроса. В 1С 8.3 индексирование временной таблицы создает реальный индекс в базе данных tempdb SQL-сервера.
Виртуальная таблица СрезПоследних является одной из самых «тяжелых». При ее вызове 1С генерирует запрос с использованием подзапроса для поиска максимальной даты на каждую комбинацию измерений. Если в регистре много данных и мало измерений, это может привести к серьезным задержкам. Рассмотрим профессиональный совет: если вам нужно получить цены для большого списка товаров, всегда сначала ограничивайте выборку в СрезПоследних с помощью условия в параметрах, как мы сделали в примере выше (Номенклатура В (ВЫБРАТЬ ВТ_Остатки.Номенклатура ИЗ ВТ_Остатки)).
При работе в базах с настроенным RLS (ограничение доступа на уровне записей) использование слова РАЗРЕШЕННЫЕ добавляет к вашему запросу множество дополнительных условий, которые накладываются на каждую таблицу. Если в запросе много соединений, итоговый текст SQL-запроса может вырасти в разы. В таких случаях декомпозиция через временные таблицы становится не просто рекомендацией, а обязательным условием. Разделив запрос, мы позволяем системе RLS отработать на маленьких выборках, что существенно повышает общую стабильность системы.
Для эффективной работы с данными в 1С мы должны придерживаться следующих правил:
Период, Условие) для предварительной фильтрации данных.МенеджерВременныхТаблиц для сложных расчетов.ИНДЕКСИРОВАТЬ ПО для полей связи во временных таблицах.ГДЕ, стараясь перенести их в параметры виртуальных таблиц.Соблюдение этих простых, но мощных техник позволит вашим отчетам и обработкам работать максимально быстро даже на очень больших объемах данных, обеспечивая комфортную работу пользователей в системе 1С.