При разработке на платформе 1С программисты часто сталкиваются с задачей массового обновления реквизитов справочника на основе данных из внешних источников (Excel, XML, JSON или внешние базы данных). Когда количество записей исчисляется тысячами, стандартный перебор в цикле с использованием методов поиска начинает работать крайне медленно. Рассмотрим типичную ситуацию: обновление 5000 элементов справочника может занимать около минуты, хотя при правильном подходе эта операция должна выполняться практически мгновенно. Разберем по шагам, какие ошибки приводят к замедлению и как их исправить.
Проанализируем фрагмент кода, который часто становится причиной «тормозов». Представим, что у нас есть таблица значений СписокНоменклатура, полученная запросом, и внешняя таблица Таб с новыми данными. Основная ошибка заключается в получении кода для поиска следующим образом:
Кол = СписокНоменклатура.Количество();
Для Индекс = 0 По Кол - 1 Цикл
// ОШИБКА: Обращение к реквизиту через точку от ссылки в цикле
КодДляПоиска = СписокНоменклатура[Индекс].Номенклатура.Код1С;
СтрокаДанных = Таб.Найти(КодДляПоиска, "Код");
// ... дальнейшая обработка
КонецЦикла;
Выясним причину, почему это работает медленно. Когда мы обращаемся к СписокНоменклатура[Индекс].Номенклатура, мы получаем СправочникСсылка. Как только мы добавляем точку и запрашиваем Код1С, платформа 1С вынуждена обратиться к базе данных (или к объектному кэшу), чтобы получить данные этого объекта. Если в цикле 5000 итераций, система выполнит 5000 микро-запросов к БД. Это и есть главная причина замедления.
Решение: Все необходимые данные для сопоставления (коды, артикулы, наименования) нужно получать сразу в запросе и выгружать в таблицу значений. Посмотрим на правильный пример формирования исходных данных:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Номенклатура,
| Спр.Код1С КАК Код1С
|ИЗ
| Справочник.Номенклатура КАК Спр
|ГДЕ
| Спр.ОстаткиВыгрузить = ИСТИНА";
СписокНоменклатура = Запрос.Выполнить().Выгрузить();
// Теперь в цикле мы обращаемся к колонке таблицы, а не через точку от ссылки
Для Каждого СтрокаСписка Из СписокНоменклатура Цикл
КодДляПоиска = СтрокаСписка.Код1С; // Мгновенное получение данных из памяти
СтрокаДанных = Таб.Найти(КодДляПоиска, "Код");
КонецЦикла;
Метод Найти() для таблицы значений без индексов работает по принципу линейного перебора. Для каждой из 5000 записей первой таблицы система просматривает в среднем половину строк второй таблицы. Если во второй таблице тоже 5000 строк, общее количество операций будет огромным.
Решение: Обязательно используем индексирование колонок в таблице значений, по которым осуществляется поиск. Проанализируем, как это сделать:
Таб.Индексы.Добавить("Код");
// Теперь метод Найти() будет использовать хеш-таблицу для мгновенного поиска
Элемент = Таб.Найти(Код1С, "Код");
Важно отметить, что метод НайтиСтроки() также начинает работать быстрее при наличии индекса, однако он возвращает массив строк, что требует дополнительных проверок на количество найденных элементов.
Если вам нужно сопоставить данные максимально быстро и поиск выполняется только по одному уникальному ключу, использование объекта Соответствие может быть эффективнее таблицы значений. Поиск в соответствии реализован на уровне платформы как хеш-таблица со сложностью O(1).
Рассмотрим пример реализации:
ДанныеВнешние = Новый Соответствие;
Для Каждого Стр Из Таб Цикл
ДанныеВнешние.Вставить(Стр.Код, Стр); // Ключ — Код, Значение — вся строка или структура
КонецЦикла;
Для Каждого СтрНом Из СписокНоменклатура Цикл
СтрокаДанных = ДанныеВнешние[СтрНом.Код1С];
Если СтрокаДанных <> Неопределено Тогда
// Обработка
КонецЕсли;
КонецЦикла;
Если данные внешней таблицы Таб уже загружены в 1С, наиболее производительным способом будет использование механизма запросов. Мы можем передать таблицу значений в запрос как параметр, поместить ее во временную таблицу и выполнить соединение на уровне СУБД.
Разберем этот алгоритм по шагам:
Новый ОписаниеТипов), иначе запрос не сможет создать временную таблицу.МенеджерВременныхТаблиц.ЛЕВОЕ СОЕДИНЕНИЕ справочника с этой временной таблицей.
МВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МВТ;
// Передаем внешнюю таблицу в запрос
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВтВнешниеДанные ИЗ &ВнешняяТаблица КАК ВнешняяТаблица";
Запрос.УстановитьПараметр("ВнешняяТаблица", Таб);
Запрос.Выполнить();
// Основной запрос сопоставления
Запрос.Текст =
"ВЫБРАТЬ
| ВтВнешниеДанные.РесурсДляОбновления КАК НовыйРеквизит,
| Номенклатура.Ссылка КАК СсылкаНаОбъект
|ИЗ
| ВтВнешниеДанные КАК ВтВнешниеДанные
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВтВнешниеДанные.Код = Номенклатура.Код1С";
Выборка = Запрос.Выполнить().Выбрать();
Этот метод позволяет переложить всю тяжелую работу по сопоставлению данных на SQL-сервер, который оптимизирует соединение таблиц гораздо эффективнее, чем встроенный язык 1С.
Даже если мы нашли нужные объекты мгновенно, запись 5000 элементов справочника может занять длительное время из-за работы триггеров БД, проверок в модулях объекта и регистрации изменений для обмена данными. Чтобы ускорить этот процесс, проанализируем следующие правила:
1. Проверка модифицированности. Никогда не вызывайте метод Записать(), если данные фактически не изменились. Сравните текущее значение реквизита в базе с новым значением.
2. Использование режима загрузки данных. Если бизнес-логика позволяет пропустить стандартные проверки, используйте свойство ОбменДанными.Загрузка = Истина. Это значительно ускоряет запись.
3. Группировка в транзакции. Запись каждого объекта — это отдельная транзакция в SQL. Обернув цикл записи в одну большую транзакцию (или разбивая на пакеты по 500-1000 объектов), вы сократите накладные расходы на фиксацию данных.
НачатьТранзакцию();
Пока Выборка.Следующий() Цикл
Объект = Выборка.СсылкаНаОбъект.ПолучитьОбъект();
Если Объект.Реквизит <> Выборка.НовыйРеквизит Тогда
Объект.Реквизит = Выборка.НовыйРеквизит;
Объект.ОбменДанными.Загрузка = Истина;
Объект.Записать();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Соблюдение этих простых, но фундаментальных правил позволит вам сократить время выполнения обработки с нескольких минут до нескольких секунд, обеспечивая стабильную работу системы даже при больших объемах данных.