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

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

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

Выясним причину ошибки

Проанализируем исходный код, который часто приводит к подобной проблеме. Когда мы создаем набор записей, вызываем метод Прочитать(), а затем в цикле добавляем новые строки методом Добавить(), мы рискуем создать дубликаты внутри самого объекта в оперативной памяти.

Важно понимать: параметр Замещать = Истина в методе Записать() работает не для каждой отдельной строки, а для всего отбора. Если отбор не установлен, платформа при записи пытается очистить весь регистр и записать туда содержимое набора. Если в наборе (после выполнения Прочитать() и ручного добавления) оказались две строки с одинаковыми измерениями, платформа выдаст ошибку еще до момента физической записи в базу данных, так как сам набор записей не может содержать дублирующиеся ключи.

Решение 1: Использование таблицы значений и метода Загрузить()

Рассмотрим наиболее эффективный способ массовой записи, который позволяет избежать раздувания кода и ручного обхода строк. Этот метод заключается в предварительной подготовке данных в таблице значений (ТЗ) и последующей загрузке в набор.

Выполним следующие шаги:

  1. Создадим таблицу значений, структура которой полностью совпадает с измерениями и ресурсами регистра.
  2. Заполним ТЗ необходимыми данными.
  3. Используем метод Свернуть(), чтобы гарантированно исключить дубли по измерениям.
  4. Загрузим ТЗ в набор записей.

// 1. Подготовка таблицы значений
ТЗ_Данных = Новый ТаблицаЗначений;
ТЗ_Данных.Колонки.Добавить("Измерение1", Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг"));
// Добавьте остальные измерения и ресурсы...

Для Каждого Документ Из МассивДокументов Цикл
    НоваяСтрока = ТЗ_Данных.Добавить();
    НоваяСтрока.Измерение1 = Документ;
КонецЦикла;

// 2. Исключение дублей в самой ТЗ
ТЗ_Данных.Свернуть("Измерение1"); 

// 3. Запись в регистр
НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();
// Если нужно перезаписать только часть данных, установите НаборЗаписей.Отбор
НаборЗаписей.Загрузить(ТЗ_Данных);
НаборЗаписей.Записать(Истина);

Решение 2: Оптимизация через использование «Соответствия» (Map)

Если нам необходимо добавить новые записи, сохранив при этом старые, но мы не хотим использовать медленный метод МенеджерЗаписи в цикле, проанализируем ситуацию с точки зрения использования оперативной памяти. Мы можем использовать объект Соответствие для быстрой проверки наличия записей.

Рассмотрим алгоритм:


НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();
// Если регистр большой, лучше прочитать только нужный срез данных через Отбор
НаборЗаписей.Прочитать();

СуществующиеЗаписи = Новый Соответствие;
Для Каждого Запись Из НаборЗаписей Цикл
    СуществующиеЗаписи.Вставить(Запись.Измерение1, Истина);
КонецЦикла;

Для Каждого Документ Из МассивДокументов Цикл
    Если СуществующиеЗаписи.Получить(Документ) = Неопределено Тогда
        НовЗапись = НаборЗаписей.Добавить();
        НовЗапись.Измерение1 = Документ;
        // Помечаем, что теперь эта запись в наборе есть
        СуществующиеЗаписи.Вставить(Документ, Истина);
    КонецЕсли;
КонецЦикла;

НаборЗаписей.Записать(Истина);

Решение 3: Порционная запись для больших объемов данных

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

Посмотрим на пример реализации записи порциями по 1000 строк:


Счетчик = 0;
Порция = 1000;
НаборЗаписей = РегистрыСведений.РС.СоздатьНаборЗаписей();

Для Каждого Документ Из МассивДокументов Цикл
    НовЗапись = НаборЗаписей.Добавить();
    НовЗапись.Измерение1 = Документ;
    Счетчик = Счетчик + 1;
    
    Если Счетчик % Порция = 0 Тогда
        НачатьТранзакцию();
        Попытка
            // Записываем порцию БЕЗ замещения всего регистра (Ложь), 
            // либо с четко установленным отбором
            НаборЗаписей.Записать(Ложь); 
            ЗафиксироватьТранзакцию();
            НаборЗаписей.Очистить();
        Исключение
            ОтменитьТранзакцию();
            // Логирование ошибки
        КонецПопытки;
    КонецЕсли;
КонецЦикла;

// Дозаписываем остаток
Если НаборЗаписей.Количество() > 0 Тогда
    НаборЗаписей.Записать(Ложь);
КонецЕсли;

Важные рекомендации по производительности

При массовой работе с независимыми регистрами сведений следует учитывать несколько технических аспектов:

  1. Транзакции: Всегда оборачивайте массовую запись в явную транзакцию (команды НачатьТранзакцию() и ЗафиксироватьТранзакцию()). Это значительно ускоряет процесс, так как СУБД фиксирует изменения один раз в конце, а не после каждой строки.
  2. Блокировка данных: Чтобы избежать ошибок типа «Deadlock» (взаимная блокировка), перед записью набора рекомендуется устанавливать объект БлокировкаДанных. Это гарантирует, что другие фоновые процессы не попытаются изменить те же записи в момент вашей работы.
  3. Отбор: Если вы используете НаборЗаписей.Записать(Истина), всегда проверяйте установленный НаборЗаписей.Отбор. Без установленного отбора платформа очистит абсолютно все данные в регистре перед записью новых.
  4. Временные таблицы: Если данных крайне много, рассмотрите вариант загрузки данных во временную таблицу через запрос, соединение ее с основной таблицей регистра для поиска отсутствующих записей, и последующую выгрузку результата для записи.

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

← На главную