Обязателен ли индекс для реквизита при использовании метода НайтиПоРеквизиту() в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик 1С рано или поздно сталкивается с оптимизацией запросов и кода. Вопрос производительности базы данных напрямую связан с правильным использованием индексов. Начинающие программисты, а также специалисты, перешедшие с платформы 1С:Предприятие 7.7, часто задаются вопросом: обязательно ли индексировать реквизит справочника (или другого объекта), если по нему планируется выполнять поиск с помощью метода НайтиПоРеквизиту()? В этой статье мы подробно разберем, как устроен этот механизм изнутри, почему старые привычки могут вводить в заблуждение и как платформа 1С:Предприятие 8.х взаимодействует с СУБД под капотом.

Истоки стереотипа: как это было в 1С 7.7 и что изменилось в 1С 8.х

Для начала проанализируем, откуда берется убеждение об обязательности индексирования. В старой платформе 1С:Предприятие 7.7 метод НайтиПоРеквизиту() имел жесткое системное ограничение. Если у реквизита справочника в конфигураторе не было установлено свойство «Сортировка» (которое инициировало создание индекса на уровне базы данных), попытка вызвать метод приводила к ошибке времени выполнения. Программа просто отказывалась выполнять поиск без физического индекса.

С переходом на платформу 1С:Предприятие 8 ситуация в корне изменилась. Разработчики платформы сняли это жесткое ограничение. В современных версиях платформы (8.2, 8.3 и новее) метод НайтиПоРеквизиту() успешно отработает для любого реквизита, независимо от того, установлено ли для него свойство «Индексировать» в значение «Индексировать» или оставлено значение по умолчанию «Не индексировать».

Именно поэтому у многих разработчиков возникает когнитивный диссонанс: код компилируется, поиск выполняется, ошибок нет, хотя индекс отсутствует. Выясним причину такого поведения платформы.

С чем часто путают: где индекс в 1С 8.х действительно обязателен

Часто разработчики путают поведение метода НайтиПоРеквизиту() с другим популярным объектным методом — Выбрать(). Давайте посмотрим на синтаксис метода выборки у менеджера справочника:

Выборка = Справочники.Номенклатура.Выбрать(Родитель, Владелец, Отбор, Порядок);

Если мы попытаемся использовать параметр Отбор для метода Выбрать(), передав туда структуру с неотсортированным (непроиндексированным) реквизитом, платформа незамедлительно выбросит исключение вида: «Недопустимое значение параметра (параметр номер '3')». Это происходит потому, что метод Выбрать() с отбором на уровне платформы жестко требует наличия индекса для всех полей, входящих в отбор (за исключением стандартных полей, таких как Родитель или Владелец, которые индексируются платформой автоматически).

Таким образом, требование об обязательном индексировании реквизитов для отборов действительно существует в 1С 8.х, но относится оно к методу Выбрать(), а не к НайтиПоРеквизиту().

Как метод работает под капотом СУБД

Рассмотрим детально, что происходит на уровне СУБД (например, MS SQL Server или PostgreSQL), когда мы вызываем метод НайтиПоРеквизиту(). Пусть у нас есть справочник Контрагенты и мы ищем элемент по реквизиту ИНН:

НайденнаяСсылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", "7701010101");

Платформа 1С транслирует этот вызов в SQL-запрос к СУБД. Запрос будет выглядеть примерно следующим образом:


SELECT TOP 1
    _IDRRef
FROM
    _Reference105
WHERE
    _Fld1205 = '7701010101'

С точки зрения СУБД, этот запрос абсолютно валиден и выполним. База данных обязана вернуть результат независимо от наличия индексов. Однако способ получения этого результата будет кардинально различаться в зависимости от настроек реквизита в Конфигураторе:

  1. Если реквизит проиндексирован (свойство «Индексировать» включено): СУБД использует существующий индекс по полю _Fld1205. Произойдет операция Index Seek (поиск по индексу), которая выполняется практически мгновенно, так как база данных сразу перейдет к нужной ветке бинарного дерева индекса.
  2. Если реквизит не проиндексирован (свойство «Индексировать» выключено): СУБД не сможет использовать быстрый поиск. Ей придется выполнить операцию Table Scan (в случае кучи) или Clustered Index Scan (в случае таблицы с кластерным индексом, коей и является таблица справочника 1С). СУБД начнет последовательно перебирать все записи в таблице от первой до последней, пока не найдет совпадение.

Проанализируем разницу в производительности этих двух подходов. На маленькой базе данных или на справочнике, содержащем до пары сотен элементов, разницу во времени выполнения между полным сканированием таблицы и точечным поиском по индексу заметить невозможно — оба запроса отработают за доли миллисекунды. СУБД может даже намеренно проигнорировать существующий индекс на сверхмалых таблицах, посчитав чтение всей таблицы в оперативную память более дешевой операцией. Именно с этим эффектом столкнулся автор темы на форуме.

Однако если справочник разрастется до сотен тысяч или миллионов записей (например, справочники Номенклатура, Штрихкоды или Контрагенты в крупных базах данных), отсутствие индекса приведет к катастрофическому падению производительности. Каждый вызов НайтиПоРеквизиту() будет нагружать дисковую подсистему и процессор сервера СУБД чтением огромного объема данных с диска в оперативную память. Более того, при полном сканировании СУБД может накладывать блокировки на всю таблицу, что парализует параллельную работу пользователей в системе.

Неявное индексирование в платформе 1С

Разберем ситуации, когда индекс по реквизиту может быть создан платформой неявно, даже если разработчик установил свойство реквизита в значение «Не индексировать»:

  1. Стандартные реквизиты: Такие поля, как Ссылка, Код, Наименование, Родитель, Владелец, автоматически индексируются платформой при создании объекта метаданных. Поиск по ним всегда выполняется быстро. Например, методы НайтиПоКоду() или НайтиПоНаименованию() всегда используют предопределенные индексы СУБД.
  2. Критерии отбора: Если реквизит включен в состав какого-либо объекта метаданных типа КритерийОтбора, платформа 1С принудительно создаст индекс для этого реквизита в СУБД, игнорируя настройки самого реквизита в Конфигураторе. Это сделано для быстрого построения связанных списков.
  3. Полнотекстовый поиск: Обратите внимание, что включенное свойство «Полнотекстовый поиск» (значение «Использовать») создает собственные специализированные индексы во внешних файлах платформы (или в отдельных служебных таблицах). Эти индексы предназначены исключительно для механизмов полнотекстового поиска (например, метод ПолнотекстовыйПоиск.Найти()) и никак не помогают СУБД при стандартном SQL-запросе, генерируемом методом НайтиПоРеквизиту().

Почему «Запрос» всегда лучше методов поиска менеджера

В современной практике разработки на платформе 1С:Предприятие 8 использование методов типа НайтиПоРеквизиту(), НайтиПоКоду() или НайтиПоНаименованию() в критичных участках кода считается нерекомендуемым. Посмотрим, почему использование явных запросов предпочтительнее:

1. Борьба с проблемой N+1 запросов при обработке массивов данных.

Представим сценарий загрузки данных из внешнего файла (например, Excel), где для каждой строки файла нам нужно найти контрагента по ИНН. Если мы будем использовать НайтиПоРеквизиту() в цикле, мы породим сотни или тысячи отдельных SQL-запросов к серверу СУБД:


Для Каждого СтрокаДанных Из ТаблицаИмпорта Цикл
    СсылкаКонтрагента = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", СтрокаДанных.ИНН);
    // Обработка данных
КонецЦикла;

Каждое обращение к базе данных тратит время на сетевой обмен между сервером приложений 1С и сервером СУБД, компиляцию плана запроса и транзакционные издержки. Гораздо эффективнее собрать все ИНН в массив и выполнить один-единственный запрос с использованием конструкции ГДЕ ИНН В (&МассивИНН):


МассивИНН = ТаблицаИмпорта.ВыгрузитьКолонку("ИНН");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Контрагенты.Ссылка КАК Ссылка,
    |	Контрагенты.ИНН КАК ИНН
    |ИЗ
    |	Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |	Контрагенты.ИНН В(&МассивИНН)";

Запрос.УстановитьПараметр("МассивИНН", МассивИНН);
ТаблицаРезультатов = Запрос.Выполнить().Выгрузить();

Такой подход сокращает время выполнения операции в десятки и сотни раз.

2. Проблема неявного поведения при наличии дублей.

Метод НайтиПоРеквизиту() всегда возвращает только одну ссылку, даже если в базе данных существует несколько элементов с одинаковым значением реквизита. Какую именно ссылку вернет метод — гарантировать невозможно. Это зависит от физического порядка записей в таблице или индексе СУБД, который может измениться после реиндексации или обновления статистики. Использование же объекта Запрос позволяет разработчику явно управлять результатом: выбрать все дублирующиеся элементы, отсортировать их по определенному правилу или использовать агрегатные функции.

Резюме и практические рекомендации

Подведем итог нашему исследованию:

← На главную