Что лучше использовать в 1С: метод НайтиСтроки() или обход таблицы в цикле?

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

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

Встроенные механизмы платформы против ручного обхода

С точки зрения теории, встроенные функции платформы 1С, написанные на уровне ядра (C++), всегда должны отрабатывать быстрее, чем эмуляция аналогичного алгоритма кодом на встроенном языке. При вызове НайтиСтроки() платформа использует внутренние оптимизированные механизмы перебора и сравнения. Однако на практике возможны нюансы.

Рассмотрим ситуацию, когда в результатах поиска оказывается подавляющее большинство строк исходной таблицы. Если после выполнения поиска вам всё равно придется обходить полученный массив в цикле для обработки данных, то первоначальная экономия времени на поиске может свестись к нулю. В таком случае обычный проход циклом по исходной таблице с проверкой условия внутри итерации отработает ничуть не хуже, а память не будет засоряться промежуточными массивами.

Влияние индексов на скорость поиска

Проанализируем главный фактор, влияющий на быстродействие — индексацию колонок таблицы значений.

Если мы просто выполняем тупой перебор большого количества строк (например, 1 миллион записей) в поиске конкретного значения, то алгоритм выполняет полное сканирование таблицы. В тестах, проведенных разработчиками, поиск одного значения в неиндексированной таблице на 1 000 000 строк может занимать значительное время (от десятков миллисекунд до нескольких секунд, в зависимости от типов данных и сложности условий).

Посмотрим на замеры производительности при поиске по колонке с типом УникальныйИдентификатор:

Казалось бы, разница очевидна, и нужно всегда использовать индексы. Но давайте выясним скрытую причину, почему это не всегда так.

Скрытые затраты: время построения индекса

Индекс — это не магическая сущность, которая появляется мгновенно. Это отсортированная структура данных (массив колонок), и на ее создание платформа тратит вычислительные ресурсы. При использовании алгоритмов быстрой сортировки время построения индекса пропорционально N * log2(N), где N — количество строк. В то время как обычный линейный поиск требует времени, пропорционального просто N.

Разберем результаты тестирования из обсуждения. При индексации колонки с 1 000 000 записей (UUID) время, затраченное на создание самого индекса, составило 4 141 мс.

Отсюда выводим важное правило: если вам нужно осуществить единоразовый поиск в таблице, создание индекса приведет к серьезной деградации производительности. Вы потратите 4 секунды на индексацию, чтобы сэкономить 74 миллисекунды на самом поиске! Индексирование оправдывает себя исключительно в тех случаях, когда поиск по одной и той же таблице значений будет выполняться многократно (в цикле или при множественных вызовах).

Рассмотрим пример кода, с помощью которого можно провести подобное тестирование:


Функция ПолучитьЗаполненнуюТЗ()
    МассивУИД = ПолучитьСписокУИД();
    ГСЧ = Новый ГенераторСлучайныхЧисел;
    ТЗ = Новый ТаблицаЗначений;
    
    Для К = 1 По 10 Цикл
        ТЗ.Колонки.Добавить("Кол" + К);
    КонецЦикла;
    
    Для К = 1 По 1000000 Цикл
        СтрТЗ = ТЗ.Добавить();
        СтрТЗ["Кол1"] = МассивУИД[ГСЧ.СлучайноеЧисло(0, МассивУИД.ВГраница())];
    КонецЦикла;
    
    УИДПоиска = МассивУИД[ГСЧ.СлучайноеЧисло(0, МассивУИД.ВГраница())];
    
    // Замер времени индексации
    ВремяНачалаЗамера = ТекущаяУниверсальнаяДатаВМиллисекундах();
    ТЗ.Индексы.Добавить("Кол1");
    ВремяОкончанияЗамера = ТекущаяУниверсальнаяДатаВМиллисекундах();
    Сообщить("Время индексации ТЗ: " + (ВремяОкончанияЗамера - ВремяНачалаЗамера));
    
    Возврат ТЗ;
КонецФункции

Функция ПолучитьСписокУИД()
    МассивУИД = Новый Массив;
    Для К = 1 По 1000 Цикл
        МассивУИД.Добавить(Новый УникальныйИдентификатор);
    КонецЦикла;
    Возврат МассивУИД;
КонецФункции

Особенности работы метода НайтиСтроки()

Чтобы принимать правильное решение об оптимизации, давайте подробнее изучим, как работает данный метод и какие у него есть ограничения:

  1. Множественный отбор: Метод принимает в качестве параметра объект Структура. Вы можете добавить в эту структуру сразу несколько свойств (имена ключей должны совпадать с именами колонок таблицы значений). Это позволяет выполнять одновременный поиск по совпадению сразу в нескольких колонках.
  2. Только точное равенство: Метод НайтиСтроки() умеет искать значения исключительно на точное равенство (подобно оператору =). Если ваша бизнес-логика требует условий вида "Больше", "Меньше", "Входит в строку" или "Не равно", данный метод вам не подойдет. В таких случаях придется использовать обход в цикле или помещать таблицу во временную таблицу базы данных и применять Запрос.
  3. Прямая связь с исходными данными: Результатом работы функции является Массив. Важно понимать, что этот массив содержит ссылки на строки оригинальной таблицы значений, а не их копии. Если мы переберем полученный массив циклом и поменяем значения в полях, данные мгновенно изменятся и в самой исходной таблице значений.
  4. Отличие от метода Найти(): Не стоит путать НайтиСтроки() с функцией Найти(). Метод Найти() возвращает только первую найденную строку и прекращает поиск. Кроме того, при неудаче он возвращает Неопределено, в то время как НайтиСтроки() вернет пустой Массив. Обязательно учитывайте это в коде: перед обращением к элементу массива по индексу всегда проверяйте Количество() > 0, иначе возникнет ошибка исполнения.

Опасности использования на тонком клиенте

Особо проанализируем ситуацию с управляемыми формами. Если вы работаете с типом ДанныеФормыКоллекция (например, с табличной частью документа, размещенной на форме) в контексте тонкого клиента, использование НайтиСтроки() может сыграть с производительностью злую шутку.

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

Подводим итоги

Так что же выбрать? Давайте резюмируем:

← На главную