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

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

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

Разбор основной ошибки: природа Набора Записей

Рассмотрим ситуацию, с которой обратился автор темы. Он пытался в цикле создавать набор записей, добавлять в него строку и записывать. Однако результат в консоли регистра его разочаровал: вместо 16 записей (от 0 до 160 с шагом 10) он видел только число 160.

Проанализируем причину. Объект НаборЗаписей в 1С — это не просто инструмент для добавления строк. Это объект, который управляет целым подмножеством данных регистра, ограниченным определенным фильтром (отбором). Когда вы вызываете метод Записать(), система выполняет следующие действия:

  1. Определяет область данных согласно установленным в НаборЗаписей.Отбор значениям.
  2. Удаляет все существующие в базе записи, подпадающие под этот отбор.
  3. Записывает в эту область те строки, которые вы добавили в объект НаборЗаписей программно.

Если отбор не установлен, 1С считает, что область действия набора — весь регистр. Таким образом, на каждой итерации цикла программа сначала очищала весь регистр, записывала одну строку и на следующей итерации снова все удаляла, чтобы записать следующую. В итоге оставалась только запись из последней итерации.

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

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


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

В данном примере мы для каждой итерации создаем уникальный отбор по времени. Теперь метод Записать() воздействует только на ту "точку" во времени, которую мы указали. Остальные записи в регистре остаются нетронутыми.

Оптимизация: Набор записей vs Менеджер записи

В ходе дискуссии было упомянуто, что для записи "по одной штуке" можно использовать МенеджерЗаписи. Давайте выясним разницу между ними, чтобы вы могли выбирать оптимальный инструмент.

Менеджер записи (РегистрыСведений.ВашРегистр.СоздатьМенеджерЗаписи()) — это объект, предназначенный для работы ровно с одной записью. Он удобен тем, что вы просто заполняете ключевые поля (период, измерения), вызываете Прочитать(), меняете ресурсы и вызываете Записать(). Однако использование менеджера записи в циклах — это плохая практика (Performance Anti-pattern). Каждый вызов Записать() инициирует отдельное обращение к СУБД, создание транзакции и обновление индексов.

Набор записей — гораздо более мощный инструмент. Если вам нужно записать 100 строк, правильнее сделать это одним набором, если они объединены общим смыслом или периодом, либо использовать одну транзакцию.

Массовая запись данных из запроса

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

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

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

Пример реализации массовой записи:


НаборЗаписей = РегистрыСведений.МойРегистр.СоздатьНаборЗаписей();
// Устанавливаем общий отбор, если данные принадлежат одной категории/периоду
НаборЗаписей.Отбор.Организация.Установить(ВыбраннаяОрганизация);

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

// Записываем всё одним махом. Замещать=Истина (по умолчанию) удалит старые записи 
// только по данной Организации.
НаборЗаписей.Записать();

Важные нюансы работы с СУБД и блокировками

Проанализируем техническую сторону вопроса, которая не всегда очевидна при написании кода. Когда мы говорим "замещение", мы должны понимать, что в терминах SQL такой операции нет. Платформа 1С выполняет команду DELETE, а затем серию команд INSERT. Это важно учитывать по следующим причинам:

1. Чтение перед записью: Если вы записываете набор записей с установленным отбором, системе нужно знать, что именно удалять. Если вы не используете Записать(Ложь), 1С предварительно считывает данные по отбору в память. Если в регистре миллионы записей, а ваш отбор слишком широкий (или отсутствует), вы рискуете перегрузить оперативную память сервера приложений.

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

3. Режим "Замещать = Ложь": Метод Записать() может принимать параметр Ложь. В этом случае 1С не будет пытаться удалять старые записи. Она просто попытается "вставить" новые. Это работает быстрее, но если в базе уже есть запись с таким же набором измерений и периодом, система выдаст ошибку уникальности. Используйте этот режим только тогда, когда вы на 100% уверены, что данных в базе еще нет (например, при первоначальном заполнении).

Рекомендации по удалению данных

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

Подводя итог, помните: НаборЗаписей — это ваше "окно" в базу данных. Его размер и положение определяются отбором. Все, что попало в это окно в базе, будет заменено тем, что вы подготовили в объекте набора в коде.

← На главную