Как правильно записывать данные из табличной части в регистр сведений набором записей?

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

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

Проблема построчной записи в цикле

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


Для Каждого Строка Из Объект.ЦеныЯндекс Цикл
    НаборЗаписей = РегистрыСведений.ЦеныЯндексМаркета.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Номенклатура.Установить(Строка.Номенклатура);
    НаборЗаписей.Отбор.Период.Установить(ТекущаяДата);
    
    НовЗапись = НаборЗаписей.Добавить();
    ЗаполнитьЗначенияСвойств(НовЗапись, Строка);
    НовЗапись.Период = ТекущаяДата;
    
    НаборЗаписей.Записать(Истина);
КонецЦикла;

Выясним причину, почему такой подход считается некорректным. При каждом вызове метода Записать(Истина) система инициирует новую транзакцию в СУБД, проверяет права доступа, выполняет блокировки таблиц и записывает данные на диск. Если в вашей табличной части 1000 строк, система выполнит 1000 тяжелых операций обмена с базой данных. Это не только замедляет работу конкретного пользователя, но и может заблокировать работу коллег.

Оптимальное решение: использование набора записей вне цикла

Для повышения производительности нам необходимо минимизировать количество обращений к базе данных. Правильный подход заключается в том, чтобы подготовить все записи в оперативной памяти и записать их одним «пакетом». Разберем этот алгоритм по шагам:

  1. Создаем объект НаборЗаписей один раз перед началом обхода строк.
  2. Устанавливаем необходимые фильтры (отборы), чтобы определить область данных, которую мы хотим обновить.
  3. В цикле добавляем новые строки в коллекцию набора, не обращаясь к базе данных.
  4. Вызываем метод Записать() один раз в самом конце.

Посмотрим на реализацию этого метода:


// 1. Создаем набор записей
НаборЗаписей = РегистрыСведений.ЦеныЯндексМаркета.СоздатьНаборЗаписей();

// 2. Устанавливаем отбор по регистратору или другим измерениям
// ВАЖНО: Если мы хотим заменить данные по конкретному документу:
НаборЗаписей.Отбор.Ссылка.Установить(Объект.Ссылка); 

// 3. Заполняем набор данными из табличной части в памяти
Для Каждого Строка Из Объект.ЦеныЯндекс Цикл
    НовЗапись = НаборЗаписей.Добавить();
    НовЗапись.Период = ТекущаяДата;
    НовЗапись.Номенклатура = Строка.Номенклатура;
    НовЗапись.Производитель = Строка.Производитель;
    НовЗапись.Цена = Строка.Цена;
    НовЗапись.Ссылка = Объект.Ссылка;
КонецЦикла;

// 4. Записываем весь массив данных одной транзакцией
Попытка
    НаборЗаписей.Записать(Истина); // Истина означает замещение данных по фильтру
    Сообщить("Данные успешно обновлены.");
Исключение
    Сообщить("Ошибка при записи: " + ОписаниеОшибки());
КонецПопытки;

Разбор мифа: нужно ли использовать Прочитать() и Очистить()?

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

Когда вы устанавливаете НаборЗаписей.Записать(Истина), платформа 1С автоматически выполняет следующие действия:

Если вы вызовете Прочитать(), система потратит время на то, чтобы выгрузить старые данные из базы в оперативную память сервера приложений, которые вы всё равно собираетесь удалить. Используйте Прочитать() только в том случае, если вам нужно проанализировать или модифицировать текущие записи (например, увеличить цену на 10% для уже существующих строк), а не просто перезаписать их.

Критически важный нюанс: Опасность пустого отбора

При использовании метода Записать(Истина) нужно быть крайне осторожными с фильтрами. Рассмотрим ситуацию: если вы забыли установить отбор (НаборЗаписей.Отбор...Установить()) и вызвали Записать(Истина), система очистит весь регистр сведений целиком перед записью ваших новых строк. Это происходит потому, что пустой отбор для системы означает «выбрать все записи таблицы».

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

Работа с периодическими регистрами

Если ваш регистр является периодическим, свойство Период играет ключевую роль. Проанализируем два сценария:

  1. Период в отборе: Если вы установили отбор по конкретной дате, Записать(Истина) удалит данные только за эту секунду (момент времени).
  2. Период вне отбора: Если вы установили отбор только по Номенклатуре, система удалит всю историю цен по этой номенклатуре за все периоды и запишет новые строки.

Всегда четко определяйте, хотите ли вы обновить срез последних данных или перезаписать всю историю изменений.

Оптимизация для больших объемов данных

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

Для этого можно использовать следующий подход:


КоличествоСтрок = Объект.ЦеныЯндекс.Количество();
РазмерПорции = 1000;
ТекущийИндекс = 0;

Пока ТекущийИндекс < КоличествоСтрок Цикл
    НаборЗаписей = РегистрыСведений.ЦеныЯндексМаркета.СоздатьНаборЗаписей();
    // Установка отборов для текущей порции, если это логически возможно
    // В данном сценарии лучше использовать Ложь в Записать(), если отборы не покрывают уникальность
    
    Для Счетчик = 1 По РазмерПорции Цикл
        Если ТекущийИндекс >= КоличествоСтрок Тогда Прервать; КонецЕсли;
        
        СтрокаТЧ = Объект.ЦеныЯндекс[ТекущийИндекс];
        НовЗапись = НаборЗаписей.Добавить();
        ЗаполнитьЗначенияСвойств(НовЗапись, СтрокаТЧ);
        
        ТекущийИндекс = ТекущийИндекс + 1;
    КонецЦикла;
    
    // Используем Ложь, если мы просто дописываем данные порциями
    НаборЗаписей.Записать(Ложь); 
КонецЦикла;

Использование блокировок данных

В многопользовательской системе крайне важно использовать объект БлокировкаДанных. Это предотвращает ситуацию, когда два пользователя одновременно пытаются изменить одни и те же записи регистра. Блокировку следует устанавливать до начала записи набора.


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ЦеныЯндексМаркета");
ЭлементБлокировки.УстановитьЗначение("Номенклатура", ВыбраннаяНоменклатура);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

Блокировка.Заблокировать();
// Теперь можно безопасно выполнять Записать()

Итоги

Мы проанализировали основные методики работы с регистрами сведений и выяснили, что НаборЗаписей является мощным инструментом для массовой обработки данных. Правильное использование отборов, отказ от лишних вызовов Прочитать() и применение пакетной записи позволяют ускорить работу алгоритмов в десятки раз, обеспечивая стабильность и отзывчивость системы 1С.

← На главную