Запрос к справочнику контрагентов с отбором по ИНН. Тысячная итерация — и пользователь жалуется, что обработка идёт уже двадцать минут. Открываете план запроса в SQL Server — там полное сканирование таблицы. Каждый раз. По миллиону строк. На каждое из тысячи обращений.
Решение очевидно: индекс по ИНН. Открываете Конфигуратор, идёте в свойства реквизита «ИНН», ставите «Индексировать» — и упираетесь в то, что конфигурация на поддержке. Снимать с поддержки ради одного индекса — это плохая практика, которую вы будете расхлёбывать при каждом обновлении ближайшие три года. А индекс нужен сейчас.
Хорошая новость: индексы можно добавлять через расширение, без снятия конфигурации с поддержки. И платформа сама перенесёт эти изменения в физическую структуру базы данных. Плохая новость: про это мало где написано, и есть подводные камни.
Когда индекс действительно нужен
Прежде чем добавлять что-то в базу, стоит убедиться, что вам это нужно. Лишний индекс — это не «бесплатное ускорение». Это дополнительная работа при каждой записи документа или элемента справочника, дополнительное место на диске, дополнительная нагрузка на пересборку статистики.
Индекс нужен, если выполняются три условия одновременно. Первое — есть запрос (или часть приложения), который страдает от медленной выборки по этому полю. Не «теоретически может пригодиться», а конкретный запрос, у которого вы видели длительность в SQL Profiler или ТЖ. Второе — этот запрос вызывается часто. Раз в день — не повод. Сто раз в час — повод. Третье — поле имеет высокую селективность. То есть отбор по нему даёт небольшой набор строк (десятки или сотни), а не половину таблицы.
Поле «Пол» (мужской/женский) индексировать почти бессмысленно — селективность 50%, СУБД всё равно прочитает половину таблицы и индекс не даст выигрыша. Поле «ИНН» индексировать имеет смысл — селективность близка к 100%, отбор возвращает один контрагент.
Перед добавлением индекса обязательно сделайте замер длительности запроса до и после. Иногда оказывается, что узкое место не в этой выборке, а где-то рядом — и индекс ничего не меняет.
Что вообще можно индексировать в 1С
Платформа 1С позволяет управлять индексами на трёх уровнях, и не для всех объектов это одинаково.
Для реквизитов справочников и документов — свойство «Индексировать» имеет три значения: «Не индексировать» (по умолчанию), «Индексировать» и «Индексировать с доп. упорядочиванием». Первое — индекса нет. Второе — индекс есть, поле само по себе. Третье — индекс по полю плюс дополнительные поля для сортировки (для документов это Дата + Ссылка, для справочников — Наименование или Код). Третий вариант пригождается для динамических списков, где сортировка идёт по комбинированному ключу.
Для измерений регистров сведений и накопления — свойство «Индексировать» работает похоже. Регистры по умолчанию строят индекс по всему набору измерений, в порядке их объявления. Если вам нужен отбор по второму измерению без первого — этот индекс не сработает, и нужно явно проиндексировать измерение.
Для ресурсов регистров индексы не строятся вообще — это значения, по ним не отбирают. Если вам нужно искать по ресурсу — это плохой знак, ресурс должен быть измерением.
Для табличных частей можно индексировать колонки. Это редко нужно, но иногда полезно для табличных частей с сотнями тысяч строк (например, если вы храните в табличной части подробную раскладку по партиям).
Как добавить индекс через расширение
Расширения конфигурации позволяют заимствовать объекты основной конфигурации и менять их свойства. В том числе свойства реквизитов. Вот пошагово.
Шаг 1. Создаёте новое расширение или открываете существующее. Тип — «Дополнение», «Адаптация» — для нашей задачи это неважно, главное, чтобы расширение поддерживало изменения структуры.
Шаг 2. Заимствуете нужный объект (справочник, документ, регистр). Через контекстное меню «Добавить в расширение» в дереве объектов основной конфигурации.
Шаг 3. В заимствованном объекте находите нужный реквизит. По умолчанию реквизиты заимствуются «как ссылки» — то есть в расширение они не копируются. Чтобы изменить свойство, нужно явно заимствовать сам реквизит: правая кнопка «Добавить в расширение». После этого реквизит появится в составе заимствованного объекта внутри расширения.
Шаг 4. Открываете свойства реквизита внутри расширения. Видите свойство «Индексировать». Меняете на нужное значение. Сохраняете расширение.
Шаг 5. Подключаете расширение к рабочей базе и обновляете конфигурацию базы данных. Платформа увидит, что в реквизите изменилось свойство «Индексировать», и при реструктуризации добавит индекс в физическую таблицу СУБД.
Реструктуризация может занять время — особенно если таблица большая. На таблице из десяти миллионов строк построение индекса занимает минуты, иногда десятки минут. На это время база заблокирована, поэтому делайте либо в окно технического обслуживания, либо после рабочего дня.
Подводные камни
Это не «положил в расширение и забыл». Несколько вещей, на которых легко споткнуться.
Свойство «Индексировать» не всегда видно для заимствованного реквизита. Если вы заимствовали реквизит через «как ссылку», вы увидите его в дереве, но изменить свойства не сможете. Нужно именно заимствовать, а не «вытащить из основной конфигурации». Разница тонкая, и в первый раз люди обычно делают неправильно. Решение — в свойствах реквизита посмотреть, что говорит платформа: если стоит «Заимствован» — значит, можно менять свойства. Если «Из основной конфигурации» — нельзя.
Реструктуризация при подключении расширения может не сработать сразу. Если вы подключили расширение и сразу запустили базу, изменение «Индексировать» применится только при следующем обновлении конфигурации БД. Запустите вручную: «Конфигурация → Обновить конфигурацию базы данных» (Ctrl+Shift+F11). На рабочей базе это нужно делать в режиме исключительного доступа — то есть пользователи в это время работать не смогут.
На большой таблице индекс строится долго и может вызвать таймаут. Платформа имеет таймаут на операции реструктуризации — обычно достаточный, но не всегда. Если ваша таблица содержит десятки миллионов строк, операция может оборваться по таймауту, и вы получите неконсистентное состояние. Решение — заранее увеличить таймаут реструктуризации в настройках сервера 1С (параметр в файле настроек кластера) или построить индекс вручную в SQL, а потом обновить расширение, чтобы платформа «увидела» уже существующий индекс. Второй вариант хитрее, но он позволяет избежать длительной остановки базы.
Изменение «Индексировать» через расширение перетаскивается при каждом обновлении основной конфигурации. Это хорошая новость — платформа не теряет ваши изменения. Но это и риск: если в основной конфигурации вендор сам добавит индекс по тому же полю, у вас может получиться дубликат на уровне СУБД (или, наоборот, конфликт). После обновления основной конфигурации проверьте, что ваш индекс остался единственным.
Как проверить, что индекс работает
Добавили индекс — проверьте, что СУБД его реально использует. Без проверки вы не знаете, помогло ваше изменение или нет.
Самый надёжный способ — посмотреть план запроса. В SQL Server Management Studio выполните тот SQL, который раньше был медленным (вы его уже видели в ТЖ или Profiler), включив «Include actual execution plan». В плане ищите оператор по интересующей таблице. Если вы видите Index Seek по нашему новому индексу — отлично, работает. Если видите Index Scan — индекс есть, но СУБД его не использует целенаправленно. Если видите Table Scan или Clustered Index Scan — индекс не используется вообще, что-то пошло не так.
В PostgreSQL то же самое делается через EXPLAIN ANALYZE. Команды: BitmapHeapScan с Index Cond — индекс работает; Seq Scan — нет.
Если индекс не используется, причин может быть несколько. Самая частая — устаревшая статистика. После создания индекса нужно обновить статистику: UPDATE STATISTICS в MS SQL, ANALYZE в PostgreSQL. Без этого СУБД не знает, что теперь индекс существует, и продолжает использовать старый план. Регулярное обслуживание базы должно делать это автоматически — про это есть отдельная статья про настройку SQL Server для 1С, где разобрана связка статистика плюс план обслуживания.
Вторая причина — запрос написан так, что индекс не подходит. Например, индекс по полю «ИНН», а в запросе условие СТРОКА(Контрагент.ИНН) = ... или ВЕРХ(ИНН) = ... — тут СУБД должна вычислить функцию для каждой строки, и индекс не используется. Лечение — либо переписать запрос без функций над индексируемым полем, либо построить функциональный индекс (что в 1С через расширение, к сожалению, нельзя — только напрямую в СУБД).
Третья причина — селективность ниже, чем СУБД ожидает. Если в плане она думает, что по нашему ИНН будет тысяча совпадений, она может выбрать сканирование вместо seek. Лечение — обновить статистику или временно использовать хинт (но это редко нужно для 1С).
Несколько правил, которые экономят время
Не индексируйте «на всякий случай». Каждый индекс — это работа на каждой записи. Если вы добавили десять индексов в один справочник, запись каждого нового элемента стала на 30–40% медленнее. На небольшой нагрузке этого не видно, на потоке — критично.
Не пытайтесь индексировать составной отбор одним индексом. Если вы регулярно отбираете по «Контрагент И Дата», это два разных индекса (или один составной, в правильном порядке полей), а не «сразу по двум». Платформа умеет составные индексы только через свойство «Индексировать с доп. упорядочиванием», и набор полей там фиксирован — обычно это не то, что вам нужно. В сложных случаях проще создать индекс напрямую в СУБД, в обход 1С.
Не забывайте про регистры сведений. Часто оказывается, что справочник можно вообще не индексировать, потому что отбор удобнее перенести в регистр сведений с правильным порядком измерений. Регистры индексируются автоматически по составу измерений в порядке их объявления. Это перекликается с темой правильного использования фильтров в запросах 1С: иногда переписать архитектуру дешевле, чем добавлять индекс к существующей.
Перед массовой правкой сделайте замер «до». Запишите длительность запроса, который вы хотите ускорить. После добавления индекса — повторите замер. Если ускорения нет — индекс был не нужен, удалите его. Лучше потерять полчаса на замеры, чем оставить в базе бесполезный индекс, который потом замедляет каждую запись.
Когда расширение не подходит
Иногда вам нужен индекс, который через расширение нельзя сделать. Например, частичный индекс (только по строкам, удовлетворяющим условию) или функциональный (по выражению). Платформа 1С таких индексов не поддерживает в принципе.
В таких случаях остаётся два варианта. Первый — создать индекс прямо в СУБД, мимо 1С. Это работает, но небезопасно: при следующей реструктуризации платформа может удалить «чужие» индексы. Чтобы этого не произошло, нужно отслеживать каждое обновление конфигурации БД и пересоздавать индекс. На длинной дистанции это превращается в постоянную работу.
Второй вариант — пересмотреть архитектуру. Часто оказывается, что нужный «частичный индекс» — это сигнал, что данные стоит вынести в отдельный регистр сведений или в отдельную таблицу через внешний источник данных. Это переделка дороже, но надёжнее: вы получаете работающее решение, которое не сломается при следующем обновлении.
Главное
Индексы в 1С — это рабочий инструмент, и использовать его можно даже на конфигурации, снятой с обновлений. Расширения дают аккуратный путь: меняется только свойство реквизита, основная конфигурация не трогается, обновляемость не страдает.
Но индекс — это не «сделал и забыл». До добавления убедитесь, что он действительно нужен (есть медленный запрос, есть селективность, есть частота вызова). После добавления убедитесь, что он действительно используется (план запроса показывает seek, а не scan). И помните, что лишние индексы дороже отсутствующих: их видно не сразу, но они каждое утро добавляют миллисекунды к каждой записи документа.
Если вы честно идёте по этой схеме, добавление одного индекса занимает полчаса, и в большинстве случаев решает конкретную пользовательскую боль. А не добавление — оставляет вас с медленным запросом, который рано или поздно дорастёт до жалобы.


