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