При работе с регистрами сведений в 1С программисты часто сталкиваются с задачей массовой загрузки данных. На первый взгляд, использование объекта РегистрСведенийНаборЗаписей кажется интуитивно понятным, однако при неправильном подходе можно столкнуться с критическими ошибками производительности или блокировками. Разберем ситуацию, когда при попытке добавить новые записи в непериодический независимый регистр возникает ошибка: «Запись с такими ключевыми полями уже существует».
Проанализируем исходный код, который часто приводит к подобной проблеме. Когда мы создаем набор записей, вызываем метод Прочитать(), а затем в цикле добавляем новые строки методом Добавить(), мы рискуем создать дубликаты внутри самого объекта в оперативной памяти.
Важно понимать: параметр Замещать = Истина в методе Записать() работает не для каждой отдельной строки, а для всего отбора. Если отбор не установлен, платформа при записи пытается очистить весь регистр и записать туда содержимое набора. Если в наборе (после выполнения Прочитать() и ручного добавления) оказались две строки с одинаковыми измерениями, платформа выдаст ошибку еще до момента физической записи в базу данных, так как сам набор записей не может содержать дублирующиеся ключи.
Рассмотрим наиболее эффективный способ массовой записи, который позволяет избежать раздувания кода и ручного обхода строк. Этот метод заключается в предварительной подготовке данных в таблице значений (ТЗ) и последующей загрузке в набор.
Выполним следующие шаги:
Свернуть(), чтобы гарантированно исключить дубли по измерениям.
// 1. Подготовка таблицы значений
ТЗ_Данных = Новый ТаблицаЗначений;
ТЗ_Данных.Колонки.Добавить("Измерение1", Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг"));
// Добавьте остальные измерения и ресурсы...
Для Каждого Документ Из МассивДокументов Цикл
НоваяСтрока = ТЗ_Данных.Добавить();
НоваяСтрока.Измерение1 = Документ;
КонецЦикла;
// 2. Исключение дублей в самой ТЗ
ТЗ_Данных.Свернуть("Измерение1");
// 3. Запись в регистр
НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();
// Если нужно перезаписать только часть данных, установите НаборЗаписей.Отбор
НаборЗаписей.Загрузить(ТЗ_Данных);
НаборЗаписей.Записать(Истина);
Если нам необходимо добавить новые записи, сохранив при этом старые, но мы не хотим использовать медленный метод МенеджерЗаписи в цикле, проанализируем ситуацию с точки зрения использования оперативной памяти. Мы можем использовать объект Соответствие для быстрой проверки наличия записей.
Рассмотрим алгоритм:
Соответствие.Соответствии.Прочитать() и защитит от дублей.
НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();
// Если регистр большой, лучше прочитать только нужный срез данных через Отбор
НаборЗаписей.Прочитать();
СуществующиеЗаписи = Новый Соответствие;
Для Каждого Запись Из НаборЗаписей Цикл
СуществующиеЗаписи.Вставить(Запись.Измерение1, Истина);
КонецЦикла;
Для Каждого Документ Из МассивДокументов Цикл
Если СуществующиеЗаписи.Получить(Документ) = Неопределено Тогда
НовЗапись = НаборЗаписей.Добавить();
НовЗапись.Измерение1 = Документ;
// Помечаем, что теперь эта запись в наборе есть
СуществующиеЗаписи.Вставить(Документ, Истина);
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать(Истина);
Проанализируем ситуацию, когда массив документов содержит сотни тысяч элементов. Попытка записать их одним набором может привести к переполнению памяти сервера или длительной блокировке таблицы, что остановит работу других пользователей. В таких случаях правильно использовать порционную запись в транзакции.
Посмотрим на пример реализации записи порциями по 1000 строк:
Счетчик = 0;
Порция = 1000;
НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();
Для Каждого Документ Из МассивДокументов Цикл
НовЗапись = НаборЗаписей.Добавить();
НовЗапись.Измерение1 = Документ;
Счетчик = Счетчик + 1;
Если Счетчик % Порция = 0 Тогда
НачатьТранзакцию();
Попытка
// Записываем порцию БЕЗ замещения всего регистра (Ложь),
// либо с четко установленным отбором
НаборЗаписей.Записать(Ложь);
ЗафиксироватьТранзакцию();
НаборЗаписей.Очистить();
Исключение
ОтменитьТранзакцию();
// Логирование ошибки
КонецПопытки;
КонецЕсли;
КонецЦикла;
// Дозаписываем остаток
Если НаборЗаписей.Количество() > 0 Тогда
НаборЗаписей.Записать(Ложь);
КонецЕсли;
При массовой работе с независимыми регистрами сведений следует учитывать несколько технических аспектов:
НачатьТранзакцию() и ЗафиксироватьТранзакцию()). Это значительно ускоряет процесс, так как СУБД фиксирует изменения один раз в конце, а не после каждой строки.БлокировкаДанных. Это гарантирует, что другие фоновые процессы не попытаются изменить те же записи в момент вашей работы.НаборЗаписей.Записать(Истина), всегда проверяйте установленный НаборЗаписей.Отбор. Без установленного отбора платформа очистит абсолютно все данные в регистре перед записью новых.Подводя итог, отметим, что для большинства повседневных задач оптимальным является Решение 1 с использованием таблицы значений и метода Загрузить(). Оно обеспечивает баланс между скоростью разработки и производительностью системы 1С.