Оптимизация запросов 1С в платформе 8.3.25: 7 мифов разработчиков

Коротко: В платформе 1С 8.3.25 появились новые механизмы оптимизации запросов: улучшенный планировщик, обновлённая работа с временными таблицами, доработки в управляемых блокировках. Но вокруг релиза появилось много мифов. Реально: индексирование временных таблиц всё ещё нужно, ВЫРАЗИТЬ для составных типов остаётся обязательным, а автоматической магии оптимизации не появилось. В статье разбираем 7 распространённых заблуждений с примерами кода и замерами.
Что нового в работе с запросами в платформе 8.3.25?
Релиз платформы 8.3.25 принёс ряд изменений, которые напрямую касаются производительности запросов. Среди ключевых улучшений — обновлённый механизм работы с временными таблицами, доработка планировщика для сложных соединений, улучшения в работе разделителей данных и оптимизация передачи параметров запроса. Однако многие разработчики восприняли эти изменения как «теперь можно писать запросы как угодно — платформа сама всё оптимизирует». Это далеко не так.
На профильных форумах, в чатах и Telegram-каналах после выхода релиза появилось множество утверждений, которые при ближайшем рассмотрении оказываются полуправдой или откровенными мифами. Давайте разберём семь самых распространённых заблуждений по порядку и посмотрим, что реально работает, а что — нет.
Если вы планируете обновление вашей системы, рекомендуем заранее изучить документацию и провести нагрузочное тестирование. Профессиональная помощь по миграции доступна через обновление 1С на бирже Koderion.
Миф 1. «Платформа 8.3.25 сама оптимизирует запросы — индексы больше не нужны»
Это самое распространённое заблуждение, которое мы встретили в обсуждениях. Действительно, в 8.3.25 улучшен планировщик, и для некоторых сценариев он действительно стал лучше выбирать порядок соединений. Но это касается СУБД-уровня и ситуаций, где статистика на стороне базы данных уже корректна.
Что реально изменилось?
Платформа теперь точнее передаёт в СУБД информацию о селективности параметров. Это значит, что MS SQL Server или PostgreSQL получает более точные подсказки о количестве строк, которые вернёт фильтр. Но это не отменяет необходимости создавать корректные индексы на уровне метаданных и индексировать временные таблицы.
Проверим на практике
// Запрос без индексирования временной таблицы
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул
|ПОМЕСТИТЬ ВТ_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ВидНоменклатуры = &ВидТовар
|;
|////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| ВТ_Номенклатура КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
| ПО ВТ.Номенклатура = Остатки.Номенклатура";
Запрос.УстановитьПараметр("ВидТовар", Перечисления.ВидыНоменклатуры.Товар);
Результат = Запрос.Выполнить();На объёме в 500 000 записей этот запрос на 8.3.25 выполнялся 4,8 секунды. Добавим индекс к временной таблице:
// Запрос с индексированной временной таблицей
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул
|ПОМЕСТИТЬ ВТ_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ВидНоменклатуры = &ВидТовар
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| ВТ_Номенклатура КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
| ПО ВТ.Номенклатура = Остатки.Номенклатура";Время выполнения — 0,9 секунды. Разница в пять раз. Вывод: индексирование временных таблиц по-прежнему критично.
Миф 2. «Функция ВЫРАЗИТЬ для составных типов больше не нужна»
Ещё один популярный миф: якобы в 8.3.25 платформа сама приводит составные типы к нужному виду. На самом деле улучшения коснулись только обработки NULL-значений в составных типах, а необходимость приведения типов через ВЫРАЗИТЬ() осталась.
Почему ВЫРАЗИТЬ всё ещё важен?
Когда в реквизите хранится составной тип (например, ссылка на разные документы), СУБД вынуждена делать JOIN со всеми таблицами возможных типов. Если мы знаем, что нужен только один конкретный тип, операция ВЫРАЗИТЬ позволяет оставить только нужное соединение.
// Плохо — JOIN со всеми типами
Запрос.Текст =
"ВЫБРАТЬ
| Движения.Регистратор.Дата КАК ДатаДокумента,
| Движения.Регистратор.Номер КАК НомерДокумента
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК Движения
|ГДЕ
| Движения.Период МЕЖДУ &НачалоПериода И &КонецПериода";
// Хорошо — JOIN только с нужным документом
Запрос.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Дата КАК ДатаДокумента,
| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Номер КАК НомерДокумента
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК Движения
|ГДЕ
| Движения.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И ТИПЗНАЧЕНИЯ(Движения.Регистратор) = ТИП(Документ.ПоступлениеТоваровУслуг)";На базе с 20 типами документов-регистраторов выигрыш от использования ВЫРАЗИТЬ может быть 10-кратным. Платформа 8.3.25 здесь ничего не меняет.
Миф 3. «Виртуальные таблицы регистров теперь работают как обычные»
В сообществе появилось утверждение, что в 8.3.25 виртуальные таблицы (.Остатки, .Обороты) обрабатываются так же эффективно, как физические таблицы регистров. Это неверно.
Как на самом деле?
Виртуальные таблицы — это конструкции, которые платформа транслирует в запросы к итоговым и основным таблицам. Если не передавать параметры в виртуальную таблицу, она вычисляется по полному набору данных, что катастрофически медленно.
// Неправильно — фильтр в ГДЕ после виртуальной таблицы
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|ГДЕ
| Остатки.Склад = &Склад
| И Остатки.Номенклатура = &Номенклатура";
// Правильно — параметры внутри виртуальной таблицы
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Склад = &Склад
| И Номенклатура = &Номенклатура) КАК Остатки";Разница в производительности на больших объёмах — десятки и сотни раз. Платформа 8.3.25 эту логику не изменила и не отменила.
Миф 4. «Подзапросы в условиях теперь не тормозят»
Утверждение, что улучшенный планировщик в 8.3.25 решает проблему вложенных подзапросов — миф. Платформа действительно стала лучше работать с некоторыми типами подзапросов, но общая рекомендация остаётся прежней: избегайте подзапросов в условиях соединений и в секции ГДЕ, используйте временные таблицы.
Сравнение подходов
// Плохо — подзапрос в условии
Запрос.Текст =
"ВЫБРАТЬ
| Заказ.Ссылка,
| Заказ.Дата
|ИЗ
| Документ.ЗаказПокупателя КАК Заказ
|ГДЕ
| Заказ.Контрагент В
| (ВЫБРАТЬ
| Контрагенты.Ссылка
| ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ГДЕ
| Контрагенты.ГоловнойКонтрагент = &ГоловнойКонтрагент)";
// Хорошо — через временную таблицу
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Контрагент
|ПОМЕСТИТЬ ВТ_Контрагенты
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ГоловнойКонтрагент = &ГоловнойКонтрагент
|
|ИНДЕКСИРОВАТЬ ПО
| Контрагент
|;
|////////////////////////////////////////
|ВЫБРАТЬ
| Заказ.Ссылка,
| Заказ.Дата
|ИЗ
| Документ.ЗаказПокупателя КАК Заказ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Контрагенты КАК ВТ
| ПО Заказ.Контрагент = ВТ.Контрагент";Это особенно актуально для типовых конфигураций, таких как 1С:Бухгалтерия на Кодерион и решения для управления, где справочники контрагентов и номенклатуры могут содержать миллионы записей.
Миф 5. «РАЗЛИЧНЫЕ можно использовать везде — это бесплатно»
Ключевое слово РАЗЛИЧНЫЕ используют для устранения дубликатов в результатах запроса. В сообществе появилось мнение, что в 8.3.25 это стало «бесплатной» операцией. Это категорически неверно.
Что происходит на самом деле?
Оператор РАЗЛИЧНЫЕ требует от СУБД отсортировать весь результирующий набор и удалить дубликаты. Для больших выборок это дорогая операция. Если дубликаты возникают из-за неправильного соединения — нужно исправлять соединение, а не «маскировать» проблему через РАЗЛИЧНЫЕ.
// Плохо — РАЗЛИЧНЫЕ маскирует проблему соединения
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Номенклатура.Ссылка,
| Номенклатура.Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК Поступления
| ПО Номенклатура.Ссылка = Поступления.Номенклатура";
// Хорошо — используем СГРУППИРОВАТЬ или EXISTS-логику
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ
| Поступления.Номенклатура
| ИЗ
| Документ.ПоступлениеТоваровУслуг.Товары КАК Поступления)";В реальном проекте при работе с задачами по 1С:ERP мы видели запрос отчёта, где замена РАЗЛИЧНЫЕ на корректное группирование сократила время с 47 секунд до 3.
Миф 6. «ОБЪЕДИНИТЬ ВСЕ и ОБЪЕДИНИТЬ работают одинаково быстро»
Многие разработчики путают ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ или считают их равнозначными. В платформе 8.3.25 разница между ними не уменьшилась — наоборот, ещё стала заметнее на больших объёмах.
В чём разница?
ОБЪЕДИНИТЬ— объединяет результаты и убирает дубликаты (требует сортировки)ОБЪЕДИНИТЬ ВСЕ— просто склеивает результаты без проверки дубликатов
Если вы заведомо знаете, что дубликатов между объединяемыми выборками не будет, всегда используйте ОБЪЕДИНИТЬ ВСЕ.
// Получение движений по двум регистрам
Запрос.Текст =
"ВЫБРАТЬ
| ""Поступление"" КАК ТипОперации,
| Поступления.Период,
| Поступления.Номенклатура,
| Поступления.Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК Поступления
|ГДЕ
| Поступления.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Расход"",
| Расходы.Период,
| Расходы.Номенклатура,
| Расходы.Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК Расходы
|ГДЕ
| Расходы.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход)";Так как фильтры в обеих выборках гарантированно не пересекаются, мы избегаем ненужной работы по дедупликации.
Миф 7. «Транзакционные блокировки и оптимизация запросов не связаны»
Седьмой миф особенно опасен в высоконагруженных системах. Многие считают, что оптимизация запроса — это только про скорость выполнения, а блокировки — отдельная тема. Но на самом деле длинный запрос внутри транзакции удерживает блокировки, что приводит к взаимоблокировкам и таймаутам у других пользователей.
Что изменилось в 8.3.25?
В новом релизе доработан механизм управляемых блокировок: уточнены сценарии эскалации и улучшена диагностика. Но фундаментальное правило осталось прежним — чем короче транзакция и быстрее запросы в ней, тем меньше блокировок.
// Установка управляемой блокировки перед чтением и записью
Процедура ОбработатьПроведениеДокумента(Отказ)
НачатьТранзакцию();
Попытка
// Блокировка только нужных данных
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
ЭлементБлокировки.УстановитьЗначение("Номенклатура", СписокНоменклатуры);
Блокировка.Заблокировать();
// Короткий, оптимизированный запрос для проверки остатков
РезультатЗапроса = ПолучитьОстаткиОптимизированно(Склад, СписокНоменклатуры);
// Запись движений
ЗаписатьДвиженияРегистра(РезультатЗапроса);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Проведение", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
ВызватьИсключение;
КонецПопытки;
КонецПроцедурыКогда мы оптимизируем запросы, мы автоматически снижаем время удержания блокировок. Особенно это важно для систем расчёта зарплаты, например в задачах по 1С:ЗУП, где параллельная работа сотрудников отдела кадров и бухгалтерии создаёт интенсивную конкуренцию за блокировки.
Найдите специалиста для решения этой задачи на koderion.ru