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