При разработке в среде 1С:Предприятие перед каждым программистом рано или поздно встает классическая дилемма: какой инструмент выбрать для записи данных в регистр сведений — РегистрСведенийМенеджерЗаписи или РегистрСведенийНаборЗаписей? Особенно остро этот вопрос звучит, когда регистр состоит всего из нескольких измерений (например, связь документов «один к одному»), и критически важно избежать ошибок уникальности, сохранив при этом высокую производительность системы.
В этой статье мы подробно проанализируем особенности обоих механизмов, разберем их поведение «под капотом» СУБД, сравним объемы кода и рассмотрим потенциальные риски, с которыми можно столкнуться на практике.
Разберем базовый вопрос: что произойдет, если попытаться записать в регистр сведений строку с набором измерений, который уже существует в базе данных? Вызовет ли это системную ошибку уникальности или же старая запись просто перезапишется новыми ресурсами?
Поведение платформы напрямую зависит от параметров, которые мы передаем методу записи. Если мы используем параметр замещения со значением Истина, платформа не выдаст ошибку, а перезапишет существующие данные. Выясним причину такого поведения для обоих объектов:
Записать(Истина) платформа выполняет поиск существующей записи по указанным измерениям. Если запись найдена, она обновляется (перезаписывается) без генерации ошибок уникальности ключа. Если передать значение Ложь, то при попытке записать дублирующийся ключ система вернет ошибку уникальности.Записать() (без параметров или с параметром Истина) работает в режиме замещения. Платформа сначала полностью удаляет из базы данных все существующие записи, которые соответствуют установленному в наборе отбору, а затем записывает новые строки, содержащиеся в наборе. Если отбор не установлен вовсе, это приведет к катастрофическим последствиям — очистке всего регистра.Посмотрим на пример кода и сравним, как выглядит одна и та же операция записи уникальной связки двух документов (например, ПоступлениеТоваровУслуг и АнализКачества) через оба механизма.
Вариант 1. Запись с использованием Менеджера записи
Этот вариант привлекает разработчиков лаконичностью кода. Нам не нужно явно объявлять отборы — достаточно заполнить свойства измерений и ресурсов объекта:
НоваяЗапись = РегистрыСведений.СоответствиеДокументов.СоздатьМенеджерЗаписи();
НоваяЗапись.ПоступлениеТоваров = ДокументПоступление;
НоваяЗапись.АнализКачества = ДокументАнализ;
НоваяЗапись.Актуально = Истина;
НоваяЗапись.Записать(Истина);
Вариант 2. Запись с использованием Набора записей
Для выполнения аналогичной задачи с помощью набора записей нам потребуется написать значительно больше строк кода, так как необходимо явно наложить отбор на измерения регистра:
НаборЗаписей = РегистрыСведений.СоответствиеДокументов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ПоступлениеТоваров.Установить(ДокументПоступление);
НаборЗаписей.Отбор.АнализКачества.Установить(ДокументАнализ);
НоваяСтрока = НаборЗаписей.Добавить();
НоваяСтрока.ПоступлениеТоваров = ДокументПоступление;
НоваяСтрока.АнализКачества = ДокументАнализ;
НоваяСтрока.Актуально = Истина;
НаборЗаписей.Записать(Истина);
На первый взгляд кажется, что использование менеджера записи выгоднее, так как код пишется быстрее и читается легче. Однако краткость кода — это едва ли не единственный плюс менеджера записи. Перейдем к анализу того, что происходит на более глубоком уровне.
Проанализируем ситуацию с точки зрения производительности СУБД. Мало кто знает, но объект РегистрСведенийМенеджерЗаписи не является самостоятельной физической сущностью на уровне базы данных. Это всего лишь высокоуровневая объектная надстройка над набором записей.
Когда мы вызываем метод МенеджерЗаписи.Записать(Истина), платформа 1С неявно генерирует и выполняет **два набора записей** последовательно:
Из-за этого механизма «двойного набора» обработчики событий ПередЗаписью и ПриЗаписи в модуле набора записей регистра сведений вызываются **дважды** за одну транзакцию записи менеджера. Если в этих обработчиках прописана тяжелая бизнес-логика (например, дополнительные проверки, запросы к другим таблицам или запись в лог), производительность системы упадет в два раза.
Рассмотрим подробнее сценарии массового изменения или добавления данных. Представим, что нам необходимо обработать и записать в регистр сведений 100 000 записей.
Если мы решим реализовать это в цикле через МенеджерЗаписи, то столкнемся со следующей проблемой: каждая итерация цикла без явно открытой транзакции будет порождать свою собственную неявную транзакцию в СУБД. Это приведет к огромному числу дисковых операций ввода-вывода (I/O) и критической загрузке транзакционного лога СУБД. База данных начнет сильно тормозить, а пользователи столкнутся с ожиданиями блокировок.
В случае применения НаборЗаписей мы можем эффективно группировать данные. Программисту доступна запись порциями (батчами) — например, по 1 000 или 5 000 строк в одном наборе:
НаборЗаписей = РегистрыСведений.СоответствиеДокументов.СоздатьНаборЗаписей();
// В цикле добавляем строки в набор, не устанавливая жестких отборов,
// если пишем порцию новых уникальных связок:
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
НоваяСтрока = НаборЗаписей.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
КонецЦикла;
// Записываем весь массив данных за одну транзакцию БЕЗ замещения старых данных:
НаборЗаписей.Записать(Ложь);
Использование записи наборов без замещения (параметр Ложь) для массовой вставки — один из самых быстрых способов наполнения регистра сведений, значительно превосходящий менеджер записи по скорости выполнения.
При работе с набором записей существует один очень серьезный риск, о котором должен помнить каждый разработчик. Как справедливо подмечают опытные специалисты, перед записью набора всегда нужно проверять установку отборов.
Если выполнить следующий код:
НаборЗаписей = РегистрыСведений.СоответствиеДокументов.СоздатьНаборЗаписей();
// Отбор не установлен!
НаборЗаписей.Записать(Истина);
Платформа воспримет это как команду: «Удалить все записи регистра, у которых измерения соответствуют пустому отбору (то есть абсолютно все записи), и записать вместо них пустой набор». В результате регистр сведений будет полностью очищен в одно мгновение. Чтобы избежать подобных фатальных ошибок, всегда делайте резервную копию базы данных перед тестированием нового кода и строго контролируйте заполнение отборов в коде.
Что касается блокировок, то метод НаборЗаписей.Прочитать(), вызванный внутри транзакции, накладывает блокировку на диапазон записей, соответствующий его отбору. При использовании менеджера записи контролировать границы блокировок СУБД гораздо сложнее, что увеличивает вероятность возникновения взаимных блокировок (deadlocks) в высоконагруженных многопользовательских системах.
Помимо вопросов производительности и блокировок, РегистрСведенийМенеджерЗаписи имеет ряд критических архитектурных ограничений, которые делают его практически неприменимым в сложных корпоративных ERP-системах:
ДополнительныеСвойства: У менеджера записи нет этой системной коллекции. Это значит, что вы не сможете передать из вызывающего кода в обработчики событий регистра (например, в процедуру ПередЗаписью модуля набора) какие-либо дополнительные параметры, управляющие логикой проведения или отключающие тяжелые проверки.ОбменДанными.Загрузка: В объекте менеджер записи невозможно установить режим загрузки данных (ОбменДанными.Загрузка = Истина). Из-за этого при миграции данных, обмене или загрузке из внешних систем у вас не получится временно заблокировать стандартные платформенные и конфигурационные проверки. Любая запись через менеджер будет выполняться со всей тяжелой сопутствующей логикой.Подведем итог нашему подробному разбору. Выбор инструмента зависит от конкретной архитектурной задачи:
РегистрСведенийМенеджерЗаписи только тогда, когда вам требуется интерактивная работа в интерфейсе (например, в формах списков, когда пользователь вручную редактирует одну конкретную строку, заполняя абсолютно все измерения регистра). Это оправдано простотой и лаконичностью кода.РегистрСведенийНаборЗаписей во всех остальных случаях, особенно при программной обработке данных, массовом заполнении регистров, переносе информации и интеграции. Набор записей обеспечивает максимальную производительность, гибкий контроль транзакций и блокировок, а также поддерживает важнейшие механизмы платформы (свойства обмена и дополнительные свойства).Помните о рекомендациях фирмы 1С: оптимальным с точки зрения производительности и стабильности базы данных всегда является использование набора записей с четко установленными отборами.