Вопрос оптимизации кода в 1С часто сводится к борьбе с так называемыми "запросами в цикле". Разработчики нередко задумываются: если мы выгружаем данные в таблицу значений и затем ищем в ней данные с помощью метода Найти(), находясь внутри другого цикла — не совершаем ли мы ту же самую ошибку? Разберем эту ситуацию детально, проанализируем механизмы работы платформы и выясним, как писать максимально производительный код.
Для начала проясним фундаментальное различие. Метод Найти() у объекта ТаблицаЗначений работает исключительно в оперативной памяти (на стороне сервера или клиента, в зависимости от того, где выполняется код). Он не обращается к SQL-серверу напрямую. Поэтому, формально говоря, использование Найти() в цикле не является запросом в цикле.
Однако отсутствие прямых запросов к базе данных еще не гарантирует высокую скорость работы. Если таблица значений содержит десятки тысяч строк, обычный последовательный перебор (который и выполняет Найти()) может существенно замедлить выполнение алгоритма. Рассмотрим, какие инструменты предоставляет нам платформа для ускорения этого процесса.
Многие начинающие разработчики путают эти два метода, но с точки зрения производительности между ними лежит пропасть. Проанализируем технические нюансы каждого метода:
Найти(): Этот метод выполняет линейный поиск. Программа просто перебирает строки одну за другой, пока не найдет подходящее значение. Главный минус в том, что Найти() не использует индексы, даже если вы их создали для таблицы. Его сложность — O(n).НайтиСтроки(): В отличие от своего собрата, этот метод умеет работать с индексами. Если в таблице значений созданы индексы по соответствующим колонкам, НайтиСтроки() выполняет поиск значительно быстрее. Сложность такого поиска приближается к O(1).Посмотрим на пример правильной подготовки таблицы значений для быстрого поиска в цикле:
// Подготовим таблицу данных
ТЗ_Цены = Новый ТаблицаЗначений;
ТЗ_Цены.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ_Цены.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
// Обязательно индексируем колонку, по которой будем искать
ТЗ_Цены.Индексы.Добавить("Номенклатура");
// При поиске в цикле используем НайтиСтроки
ПараметрыОтбора = Новый Структура("Номенклатура", ТекущийТовар);
НайденныеСтроки = ТЗ_Цены.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
Цена = НайденныеСтроки[0].Цена;
КонецЕсли;
Часто разработчики полагают, что используют "поиск в памяти", но на самом деле провоцируют сотни запросов к базе данных. Типичный пример плохой практики — использование конструкции Найти(Ссылка.Наименование) внутри цикла. Выясним причину, почему это происходит.
Когда вы обращаетесь к свойству ссылки через точку (например, ТекСсылка.Наименование), платформа 1С не просто берет строковое значение. Если этот объект еще не кэширован, платформа выполняет неявный запрос к базе данных, чтобы получить весь объект целиком. Если в вашем цикле 1000 итераций, вы получите 1000 запросов к БД. Это гораздо хуже, чем любой поиск в таблице значений.
Решение: Заранее выбирайте все нужные поля (Наименование, Артикул и т.д.) в запросе и выгружайте их в колонки таблицы значений. Работайте в цикле только с примитивными типами данных, уже находящимися в памяти.
Важно понимать, что индексация — это не бесплатная операция. Рассмотрим ситуации, когда создание индексов может быть неэффективным:
Проанализируем ситуацию: вам нужно быстро находить значение по ключу (например, цену по ссылке на номенклатуру). В этом случае использование объекта Соответствие (Map) часто оказывается эффективнее, чем ТаблицаЗначений с индексами.
Объект Соответствие внутри реализован как хеш-таблица. Поиск в нем происходит по ключу максимально быстро, и вам не нужно вручную управлять индексами. Разберем пример:
// Создаем соответствие для быстрого доступа
ЦеныСоответствие = Новый Соответствие;
Для Каждого Стр Из ТЗ_Данных Цикл
ЦеныСоответствие.Вставить(Стр.Номенклатура, Стр.Цена);
КонецЦикла;
// В основном цикле поиск будет мгновенным
Цена = ЦеныСоответствие.Получить(НужнаяНоменклатура);
Если Цена <> Неопределено Тогда
// Обработка данных
КонецЕсли;
Хотя поиск в таблице значений происходит "в памяти", возможности сервера не безграничны. Проанализируем риски при работе с миллионами строк:
При разрастании таблицы значений до гигантских размеров процесс rphost начинает потреблять значительный объем оперативной памяти. Если памяти станет недостаточно, операционная система задействует файл подкачки (swapping) на жестком диске. В этот момент любая работа "в памяти" станет работать медленнее, чем прямой запрос к SQL-серверу, так как диск — самое узкое место системы.
Золотое правило: Если объем данных в таблице значений исчисляется сотнями тысяч строк, рассмотрите возможность использования Временных таблиц в запросе. SQL-сервер гораздо лучше оптимизирован для поиска в больших массивах данных, умеет эффективно использовать кэши и индексы на уровне страниц данных.
Работа с ТаблицейЗначений в цикле — это мощный инструмент оптимизации, позволяющий избежать лишних обращений к базе данных. Чтобы этот инструмент работал эффективно, придерживайтесь следующих правил:
НайтиСтроки() вместо Найти() для больших таблиц.Индексы перед началом массового поиска.Соответствие.