Разработка и Конфигурация · 13 мин
Как ускорить поиск по таблице значений в 1С
Как ускорить поиск по таблице значений в 1С: индексы, метод НайтиСтроки, соответствия, запросы. Примеры кода, типичные ошибки и проверка результата.
Поиск по таблице значений — одна из самых частых операций в коде 1С: подбор строк по условию, сопоставление данных при обмене, свёртка итогов. Пока в таблице сотни строк, разницы между подходами почти нет. Но когда строк становятся десятки и сотни тысяч, неудачно написанный поиск превращается в главного тормоза обработки: перебор в цикле с условием внутри выполняется за квадратичное время, и отчёт, который должен формироваться секунды, «думает» минутами.
Главный источник проблемы — метод Найти() и ручной перебор строк с проверкой условия. Оба варианта линейно сканируют таблицу при каждом вызове. Если такой вызов стоит внутри цикла по другой коллекции, получается классическая схема O(n×m): на 50 000 строк и 50 000 поисков это миллиарды сравнений.
Ускорить поиск можно несколькими способами: добавить индекс по колонкам поиска, заменить повторные поиски соответствием, вынести выборку в запрос или перестроить алгоритм так, чтобы поиск выполнялся один раз. Ниже разобраны все варианты с примерами кода, условиями выбора и способами проверить, что оптимизация действительно сработала.
Как работает поиск в таблице значений и почему он тормозит
Таблица значений — это неиндексированная по умолчанию структура в памяти. Методы Найти() и НайтиСтроки() без индексов сканируют все строки последовательно, пока не найдут совпадение (или не дойдут до конца). Стоимость одного поиска пропорциональна числу строк.
Ручной перебор вида «Для Каждого Строка Из Таблицы Цикл … Если Строка.Номенклатура = Искомая Тогда» работает так же линейно, но добавляет накладные расходы на интерпретацию условий и часто содержит Прервать не сразу, а после лишних проверок. Хуже всего, когда такой перебор вложен в цикл по другой таблице — это типичный код сопоставления при загрузке данных из Excel или при обмене.
Любой метод даст приемлемое время; не усложняйте код
Заменяйте на соответствие или индекс + НайтиСтроки
Индексы или соответствие обязательны
Рассмотрите запрос с соединением вместо поиска в памяти
Способ 1. Индексы таблицы значений
Встроенный механизм ускорения — коллекция Индексы. После добавления индекса по колонке методы Найти() и НайтиСтроки() используют его и работают значительно быстрее линейного сканирования.
ТаблицаТоваров.Индексы.Добавить("Номенклатура");
ПараметрыОтбора = Новый Структура("Номенклатура", ИскомаяНоменклатура);
НайденныеСтроки = ТаблицаТоваров.НайтиСтроки(ПараметрыОтбора);
Индекс можно строить по нескольким колонкам через запятую: Индексы.Добавить("Организация, Склад"). Важно, чтобы структура отбора в НайтиСтроки() совпадала с составом индекса — тогда поиск пойдёт по индексу. Если отбор задан по другим колонкам, индекс не поможет.
Ограничения: индекс занимает память и замедляет массовое добавление строк, потому что перестраивается при изменениях. Если таблица заполняется в цикле сотнями тысяч строк, добавляйте индекс после заполнения, а не до. Для одноразового поиска индекс тоже невыгоден — его построение само по себе проход по таблице.
Способ 2. Соответствие вместо повторного поиска
Самый эффективный приём при массовом сопоставлении — один раз пройти по таблице и построить Соответствие, где ключ — значение колонки поиска, а значение — строка таблицы или массив строк. Поиск в соответствии выполняется за почти постоянное время независимо от размера.
ИндексПоКоду = Новый Соответствие;
Для Каждого СтрокаТовара Из ТаблицаТоваров Цикл
ИндексПоКоду.Вставить(СтрокаТовара.Код, СтрокаТовара);
КонецЦикла;
Для Каждого СтрокаЗагрузки Из ДанныеИзФайла Цикл
СтрокаТовара = ИндексПоКоду[СтрокаЗагрузки.Код];
Если СтрокаТовара <> Неопределено Тогда
СтрокаТовара.Цена = СтрокаЗагрузки.Цена;
КонецЕсли;
КонецЦикла;
Если по одному ключу может быть несколько строк, храните в значении соответствия массив и добавляйте в него строки. Это заменяет НайтиСтроки() со всеми найденными совпадениями.
Меняйте значение через найденную строку смело: строка таблицы значений передаётся по ссылке, поэтому изменения через СтрокаТовара отражаются в исходной таблице. Копировать строку для этого не нужно.
- 1Найдите в коде цикл, внутри которого вызывается Найти() или перебор по другой таблице
- 2Определите колонку-ключ, по которой идёт поиск
- 3Перед основным циклом постройте соответствие: ключ → строка (или массив строк)
- 4Внутри цикла замените поиск обращением к соответствию с проверкой на Неопределено
- 5Замерьте время до и после на реальном объёме данных
Способ 3. Запрос вместо поиска в памяти
Если данные таблицы значений нужно сопоставить с данными информационной базы, часто быстрее передать таблицу в запрос как временную таблицу и выполнить соединение на стороне СУБД. Запросный механизм использует индексы базы данных и обрабатывает большие объёмы эффективнее встроенного языка.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ Т.Код, Т.Цена ПОМЕСТИТЬ ВТ_Данные ИЗ &Таблица КАК Т;
|ВЫБРАТЬ Т.Код, Т.Цена, Ном.Ссылка
|ИЗ ВТ_Данные КАК Т
|ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Ном
|ПО Ном.Код = Т.Код";
Запрос.УстановитьПараметр("Таблица", ТаблицаТоваров);
Результат = Запрос.Выполнить().Выгрузить();
Этот подход особенно выгоден, когда альтернатива — цикл с запросом внутри (поиск ссылки по коду на каждой строке). Один запрос с соединением заменяет тысячи обращений к базе. Минус — дополнительный код и передача таблицы на сервер, что при клиент-серверном режиме тоже стоит времени на маленьких объёмах.
Сравнение вариантов
Подходит при многократном поиске по одним колонкам внутри одной процедуры; не окупается при разовом поиске
Максимальная скорость при массовом сопоставлении двух коллекций; требует памяти под ключи и код построения
Лучший выбор при сопоставлении с данными БД; избыточен, если обе таблицы уже в памяти
Допустим только на малых таблицах и разовых операциях; в циклах — главный источник тормозов
Способ 4. Сортировка и обход двух таблиц за один проход
Когда нужно сопоставить две большие таблицы по ключу, а соответствие строить нежелательно (например, ключи сложные составные), работает классический приём слияния: отсортировать обе таблицы по ключу и идти по ним двумя указателями. Сложность — сортировка плюс один проход, что намного быстрее квадратичного перебора.
В 1С сортировка выполняется методом Таблица.Сортировать("Код"). Дальше в цикле сравниваете текущие ключи и двигаете указатель той таблицы, где ключ меньше. Код длиннее, чем с соответствием, но не требует дополнительной памяти под хеш-таблицу и хорошо работает на очень больших объёмах.
На практике этот способ нужен редко — обычно соответствие проще и быстрее. Держите его в арсенале для случаев, когда ключ не укладывается в простое значение или таблицы уже отсортированы по нужному полю.
Как проверить, что оптимизация сработала
Не оптимизируйте вслепую: сначала замерьте, где именно тратится время. Простейший способ — обернуть подозрительный участок в замер через ТекущаяУниверсальнаяДатаВМиллисекундах() и вывести длительность. Для глубокого анализа используйте замер производительности в конфигураторе (Отладка → Замер производительности) — он покажет конкретные строки кода и количество вызовов.
Проверяйте на реальном объёме данных, а не на тестовых ста строках: квадратичные алгоритмы на малых данных выглядят безобидно. Если обработка в боевой базе работает с 100 000 строк, тестируйте на сопоставимом объёме в копии базы.
Как проверить результат
Сократилось в разы при том же результате
Время не изменилось — оптимизировали не то место
Найденные строки и итоговые данные совпадают со старой версией
Расхождения — ошибка в ключе соответствия или отборе
Поисковый метод вызывается один раз при построении индекса/соответствия
Найти() по-прежнему вызывается внутри цикла тысячи раз
Рост умеренный и освобождается после обработки
Память растёт — соответствие или таблица копируются лишний раз
Типичные ошибки и ограничения
Самая частая ошибка — поиск по неуникальному ключу через Найти(), который возвращает только первую строку. Если по ключу может быть несколько совпадений, используйте НайтиСтроки() или соответствие с массивом значений, иначе часть данных молча потеряется.
Вторая группа ошибок — типы данных ключа. Поиск по строковому коду не найдёт числовое значение, а сравнение строк чувствительно к пробелам и регистру в соответствии. Перед построением соответствия приводите ключи к единому виду: СокрЛП(), единый регистр, явное преобразование типа.
Третья ошибка — копирование таблицы «на всякий случай» через Скопировать() внутри цикла или передача большой таблицы с клиента на сервер при каждом вызове функции. Копирование большой таблицы само по себе дорогая операция, которая сведёт на нет выигрыш от быстрого поиска.
Как выбрать подход под свою задачу
Выбор зависит от трёх факторов: сколько раз выполняется поиск, где лежат данные для сопоставления и насколько велика таблица. Универсального ответа нет, но маршрут выбора простой.
Как выбрать действие
Если сомневаетесь, начните с соответствия: в подавляющем большинстве сценариев массового поиска оно даёт максимальный выигрыш при минимальных изменениях кода. Индексы и запросы подключайте там, где соответствие не подходит по условиям задачи.
Что важно запомнить
Тормозит не сам поиск, а его повторение в цикле. Один проход для построения соответствия или индекса заменяет тысячи линейных сканирований. Сопоставление с базой делайте запросом, результат проверяйте замером на реальном объёме и следите за типами и уникальностью ключа.
Частые вопросы
Что быстрее: НайтиСтроки с индексом или соответствие?
На единичном поиске разница невелика, на массовом соответствие обычно быстрее, потому что обращение к нему — минимальная операция без вызова встроенного метода и создания структуры отбора. Зато индекс не требует писать код построения и работает с составными отборами из коробки. Для цикла на десятки тысяч итераций берите соответствие, для пары сотен поисков достаточно индекса.
Почему НайтиСтроки не находит строку, хотя значение точно есть?
Чаще всего несовпадение типов или представления: число против строки, лишние пробелы, разный регистр букв, неразрывные пробелы в данных из файлов. Проверьте тип колонки и значения отбора, приведите оба к одному виду через СокрЛП и явное преобразование. Также убедитесь, что в структуре отбора имя колонки написано точно как в таблице.
Стоит ли добавлять индекс перед заполнением таблицы?
Нет. При добавлении каждой строки индекс перестраивается, и заполнение большой таблицы заметно замедлится. Правильный порядок: сначала полностью заполнить таблицу, затем добавить индекс, затем выполнять поиски. Исключение — если строк немного, разница будет незаметна в любом порядке.
Как искать сразу по нескольким условиям?
Передайте в НайтиСтроки структуру с несколькими полями — все условия объединяются по «И». Для ускорения добавьте составной индекс по этим же колонкам в том же порядке. Если условия сложные (диапазоны, «ИЛИ», частичное совпадение), НайтиСтроки не подойдёт: используйте запрос к таблице или перебор с условием, либо выгрузите данные в запрос.
Можно ли искать по таблице значений запросом без базы данных?
Да. Таблицу значений можно передать в запрос параметром и поместить во временную таблицу, а дальше использовать соединения, группировки и условия языка запросов — всё это выполнится без обращения к данным справочников, если они не нужны. Это удобно для сложных выборок из одной-двух таблиц в памяти, хотя для простого поиска по ключу соответствие останется быстрее.
Влияет ли клиент-серверный режим на скорость поиска?
Сама таблица обрабатывается там, где выполняется код, но передача большой таблицы между клиентом и сервером — дорогая операция. Если поиск выполняется на сервере, передавайте таблицу туда один раз, а не в каждом серверном вызове из цикла. Частые серверные вызовы с передачей данных способны тормозить обработку сильнее, чем сам поиск.