Индексы в запросах 1С 8.3: временные таблицы, план выполнения и условия, при которых СУБД берёт индекс
Содержание
В тексте запроса 1С индекс задают только для временной таблицы: после выборки с ПОМЕСТИТЬ пишут ИНДЕКСИРОВАТЬ ПО Поле1, Поле2. Индексы постоянных таблиц (справочников, документов, регистров) настраивают не в запросе, а в конфигураторе свойством «Индексировать» у реквизита или измерения. Задача запроса — сформулировать условия так, чтобы СУБД эти индексы реально использовала.
Запрос, который на копии базы отрабатывает за секунду, на рабочей базе с миллионом строк может висеть минуту. Частая причина — СУБД не нашла подходящего индекса и читает таблицу целиком. Всё описанное относится к платформе 8.3, к файловому и клиент-серверному вариантам.
Какие индексы платформа создаёт без вашего участия
Разработчик 1С не пишет CREATE INDEX. Платформа сама строит индексы при реструктуризации базы, исходя из метаданных. Знать их состав нужно, чтобы понимать, по каким полям отбор будет быстрым «бесплатно».
| Объект | Что индексируется автоматически |
|---|---|
| Справочник | Ссылка; код и наименование, если они заданы в объекте |
| Документ | Ссылка; дата и номер |
| Табличная часть | Ссылка на владельца и номер строки |
| Регистр сведений | Измерения в порядке их следования в конфигураторе, у периодического — вместе с периодом |
| Регистр накопления | Таблица движений — по регистратору и номеру строки, а также по периоду; таблица итогов — по измерениям |
| Регистр бухгалтерии | Аналогично: движения по регистратору и периоду, итоги по счёту и измерениям |
Из таблицы вытекают три практических вывода:
- 🔑 отбор по ссылке, коду, номеру или регистратору всегда быстрый;
- 📌 у регистра сведений важен порядок измерений: индекс полезен, когда условие задаёт первое измерение (или первые несколько подряд);
- ⚠️ реквизит справочника или документа без флажка индексации — это полный просмотр таблицы при каждом отборе по нему.
Полнотекстовый индекс (поиск по словам в списках) к запросам отношения не имеет. Это отдельный механизм, его обновляют регламентным заданием, и на скорость ВЫБРАТЬ … ГДЕ он не влияет.
ИНДЕКСИРОВАТЬ ПО: индекс временной таблицы прямо в запросе
Временные таблицы — главный инструмент оптимизации пакетных запросов. Вы сужаете выборку на первом шаге, складываете её в ПОМЕСТИТЬ, а дальше соединяете с ней остальные таблицы. Если временная таблица большая и по ней будет соединение, индекс по полю соединения ускоряет следующий запрос пакета.
Синтаксис простой: конструкция ИНДЕКСИРОВАТЬ ПО ставится в конце запроса, который создаёт временную таблицу. В списке указывают имена полей (псевдонимы) этой временной таблицы.
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
СУММА(Товары.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Товары.Ссылка.Проведен
СГРУППИРОВАТЬ ПО
Товары.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Продажи.Номенклатура КАК Номенклатура,
ВТ_Продажи.Количество КАК Продано,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
ВТ_Продажи КАК ВТ_Продажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&КонецПериода,
Номенклатура В
(ВЫБРАТЬ
ВТ.Номенклатура
ИЗ
ВТ_Продажи КАК ВТ)) КАК Остатки
ПО ВТ_Продажи.Номенклатура = Остатки.Номенклатура
Имена документа и регистра взяты для примера: подставьте объекты своей конфигурации.
В конструкторе запроса то же самое делают мышью. На вкладке «Дополнительно» выберите «Создание временной таблицы», после чего появится вкладка «Индекс». Перенесите на неё нужные поля, и конструктор сам допишет ИНДЕКСИРОВАТЬ ПО.
Когда индекс временной таблицы нужен, а когда мешает
| Ситуация | Индексировать? |
|---|---|
| Во временной таблице тысячи строк и больше, по полю будет соединение или отбор | Да |
Таблица используется в В (ВЫБРАТЬ … ИЗ ВТ) в параметрах виртуальной таблицы | Да, по этому полю |
| В таблице десятки строк | Нет: построение индекса дороже выигрыша |
| Таблица читается один раз целиком, без соединений | Нет |
| Поле соединения составного типа | Лучше сначала привести поле через ВЫРАЗИТЬ, затем индексировать |
Индексируйте по тем полям и в том порядке, в каком они участвуют в условии ПО следующего запроса. Индекс по полям, которые потом нигде не сравниваются, только удлиняет запрос.
Как прочитать план выполнения запроса 1С
Угадывать, использует СУБД индекс или нет, бессмысленно. Надо смотреть план — то, как СУБД решила выполнить SQL-запрос, в который платформа превратила ваш текст.
Шаг 1. Включить технологический журнал с планами
Технологический журнал настраивают файлом logcfg.xml в каталоге conf платформы на сервере 1С. Минимальная настройка для сбора SQL-запросов и их планов на MS SQL Server:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="D:\TechLog" history="2">
<event>
<eq property="name" value="dbmssql"/>
</event>
<property name="all"/>
</log>
<plansql/>
</config>
Сбор планов заметно нагружает сервер и быстро заполняет диск. Включайте журнал на 10–20 минут, на время воспроизведения медленной операции, и сразу удаляйте файл logcfg.xml. Пользователей при этом выгонять не нужно: журнал подхватывается без перезапуска.
Шаг 2. Найти свой запрос
В событиях DBMSSQL есть текст SQL, длительность и план. Ищите по длительности самые тяжёлые события. В тексте вместо привычных имён будут _Reference45, _InfoRg1234, _Fld567.
Чтобы понять, какая это таблица, выполните на сервере функцию, возвращающую соответствие объектов метаданных и таблиц СУБД:
Структура = ПолучитьСтруктуруХраненияБазыДанных(, Истина);
Результат — таблица значений: имя таблицы хранения, объект метаданных, поля и индексы. Её удобно вывести в обработке и отфильтровать по имени из журнала.
Шаг 3. Оценить операции в плане
| Операция в плане | Что значит | Оценка |
|---|---|---|
| Index Seek | Поиск по индексу, читается только нужный диапазон | Хорошо |
| Index Scan / Clustered Index Scan | Просмотр всего индекса | Плохо на больших таблицах |
| Table Scan | Чтение всей таблицы | Плохо |
| Key Lookup | Дочитывание полей, которых нет в индексе | Терпимо, если строк мало |
| Nested Loops с большим числом строк | Соединение «каждый с каждым» | Повод искать причину |
Для PostgreSQL возьмите текст запроса из журнала (событие DBPOSTGRS) и выполните его в консоли СУБД с EXPLAIN ANALYZE. Вместо Index Seek там будет Index Scan using …, а вместо Table Scan — Seq Scan.
В файловом варианте плана нет. Там проверяют замером: тот же запрос на той же базе с изменённым условием. Если время упало в разы — условие стало использовать индекс.
Виртуальные таблицы регистров: отбор внутри параметров
Виртуальные таблицы Остатки, Обороты, ОстаткиИОбороты, СрезПоследних, СрезПервых уже оптимизированы платформой. Остатки на текущую дату читаются из таблицы итогов, а не суммируются по движениям. Но выигрыш сохраняется, только если отбор задан в параметре «Условие», а не в ГДЕ.
Плохо — условие после получения остатков:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
ГДЕ
Остатки.Склад = &Склад
Здесь СУБД сначала посчитает остатки по всем складам, а затем отбросит лишнее.
Хорошо — условие в параметре:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
Отбор применяется к итогам и движениям до группировки и попадает на индекс по измерениям.
Правила работы с параметрами:
- ✅ в условии используйте только поля самой виртуальной таблицы: измерения и реквизиты, доступные в её параметрах;
- ✅ для отбора по списку —
Номенклатура В (&Список)илиВ (ВЫБРАТЬ … ИЗ ВТ)с индексированной временной таблицей; - ❌ не соединяйте виртуальную таблицу с другой таблицей, рассчитывая, что соединение само ограничит выборку: остатки сначала посчитаются по всему регистру;
- ❌ не указывайте пустой период у
СрезПоследнихиОстатки, если вам нужна конкретная дата: пустое значение означает «на текущий момент».
Для регистров сведений действует то же правило, плюс порядок измерений. Если в регистре измерения «Организация, Номенклатура», а вы отбираете только по номенклатуре, индекс по первому измерению не поможет. Тогда спасает свойство «Индексировать» у второго измерения.
Свойство «Индексировать» в конфигураторе
Если медленный отбор идёт по реквизиту без индекса, план это покажет как Scan по таблице объекта. Индекс добавляют в метаданных.
- Откройте конфигуратор и найдите объект в дереве конфигурации.
- Откройте палитру свойств нужного реквизита или измерения.
- В свойстве «Индексирование» выберите
ИндексироватьилиИндексировать с доп. упорядочиванием. У измерений регистров это флажок «Индексировать». - Сохраните конфигурацию и выполните
Конфигурация → Обновить конфигурацию базы данных(F7).
Обновление структуры базы требует монопольного доступа: все пользователи должны выйти. Делайте это в нерабочее время и после резервной копии. На больших таблицах построение индекса занимает заметное время, его нужно заложить в окно обслуживания.
Какой вариант индексирования выбрать
| Вариант | Когда выбирать |
|---|---|
| Не индексировать | Поле не участвует в отборах и соединениях |
| Индексировать | Отбор и соединения по полю в запросах |
| Индексировать с доп. упорядочиванием | Отбор по полю в динамическом списке с сортировкой по коду или наименованию — в индекс добавляется поле основного представления |
Не индексируйте всё подряд. Каждый индекс замедляет запись: при проведении документа СУБД обновляет все индексы затронутых таблиц. Строки неограниченной длины индексировать нельзя вовсе.
В типовой конфигурации на поддержке изменение свойств объекта требует включить возможность изменения. Это усложняет последующие обновления: при каждом релизе придётся сравнивать и объединять изменения. Сначала проверьте, нельзя ли переписать сам запрос.
Если база давно не обслуживалась, индексы могут быть фрагментированы. В клиент-серверном варианте их перестраивают регламентом СУБД. В файловом — через Администрирование → Тестирование и исправление в конфигураторе, флажок «Реиндексация таблиц информационной базы» (требует монопольного режима).
Условия, при которых индекс есть, а СУБД его не берёт
Индекс существует, но план всё равно показывает Scan. Это значит, что условие сформулировано так, что СУБД не может искать по индексу диапазоном.
| Как написано | Почему не работает | Как переписать |
|---|---|---|
НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День | Функция над полем: индекс по дате не применим | Док.Дата МЕЖДУ &НачалоДня И &КонецДня |
Спр.Наименование ПОДОБНО "%болт%" | Шаблон начинается с % | По возможности ПОДОБНО "Болт%" или отбор по коду/артикулу |
Т.Склад = &С1 ИЛИ Т.Контрагент = &К | ИЛИ по разным полям ломает поиск по одному индексу | Два запроса через ОБЪЕДИНИТЬ ВСЕ |
Т.Регистратор.Дата > &Д | Разыменование составного поля: соединение со всеми таблицами документов | Отбор по периоду регистра или ВЫРАЗИТЬ(Т.Регистратор КАК Документ.…) |
Соединение с подзапросом (ВЫБРАТЬ … СГРУППИРОВАТЬ ПО …) | У подзапроса нет индекса, а статистику СУБД оценивает плохо | Вынести подзапрос во временную таблицу с ИНДЕКСИРОВАТЬ ПО |
| Сравнение поля со значением другого типа | Неявное приведение типов на стороне СУБД | Передавать параметр того же типа, что поле |
Отдельный случай — ограничение доступа на уровне записей (RLS). Оно дописывает в запрос свои условия, и под пользователем запрос выполняется медленнее, чем под администратором. Проверяйте скорость под тем пользователем, у которого «тормозит», и смотрите план именно его сеанса.
Ошибки при оптимизации запросов через индексы
- 🧱 Индекс ради индекса. Флажок «Индексировать» на десятке реквизитов документа замедляет проведение у всех пользователей, а выигрыш получают один-два отчёта.
- 🔀 Порядок полей в ИНДЕКСИРОВАТЬ ПО не совпадает с условием соединения. Если соединяете по складу и номенклатуре, первым в индексе ставьте то поле, которое есть во всех условиях.
- 📅 Отбор по
Ссылка.Датав табличной части без ограничения по периоду в регистре. Для отчётов по продажам часто быстрее брать данные из регистра с отбором по периоду, а не из табличных частей документов. - 🧮 Запрос в цикле. Сто одинаковых запросов с разными параметрами медленнее одного запроса с
В (&Список), даже если каждый попадает в индекс. - 🧪 Замер на маленькой копии. На базе в 10 тысяч строк Scan и Seek неотличимы. Проверяйте на копии рабочей базы с реальным объёмом данных.