При оптимизации кода и переводе конфигурации на управляемый режим блокировок разработчики часто сталкиваются с вопросом: требуется ли перед модификацией независимого непериодического регистра сведений вручную создавать объект БлокировкаДанных, если запись выполняется через РегистрСведенийМенеджерЗаписи? Также возникает резонное опасение: не приведет ли вызов менеджера записи к блокировке соседних строк или всей таблицы целиком?
Разберем детально механизм работы управляемых блокировок платформы 1С:Предприятие при работе с записями регистров, проанализируем поведение СУБД и выясним, в каких сценариях явная блокировка действительно необходима, а где она является избыточной.
Короткий и однозначный ответ на исходный вопрос: нет, при простой записи явная управляемая блокировка не нужна. Код с созданием объекта БлокировкаДанных непосредственно перед вызовом МенеджерЗаписи.Записать(Истина) выполняет абсолютно дублирующую работу.
Рассмотрим, как платформа 1С обрабатывает запись на уровне транзакции:
Записать().РегистрСведенийМенеджерЗаписи всегда строго позиционирован на одну конкретную строку, состав которой однозначно определяется переданными значениями измерений (ключом записи).Посмотрим на исходный пример кода и оптимизируем его, убрав избыточные конструкции:
НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);
МенеджерЗаписи = РегистрыСведений.КакойТоРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Измерение1 = Контрагент;
МенеджерЗаписи.Измерение2 = Договор;
МенеджерЗаписи.Реквизит1 = "Какой-то комментарий";
// Явная блокировка здесь не требуется:
// вызов Записать() автоматически заблокирует запись по Измерение1 и Измерение2
МенеджерЗаписи.Записать(Истина);
ЗафиксироватьТранзакцию();
Выясним причину, по которой механизм БлокировкаДанных вообще существует в платформе. Явная установка исключительной блокировки требуется в классическом паттерне «ответственного чтения» (Read-Modify-Write).
Если перед выполнением записи вам необходимо сначала прочитать текущие данные регистра (или остатки/значения реквизитов), проверить бизнес-логику на основе этих данных и лишь затем записать новое значение, возникает уязвимость для состояния гонки (Race Condition):
Read Committed (чтение зафиксированных данных). Обычный запрос или вызов метода Прочитать() у менеджера записи не удерживает блокировок на уровне СУБД и не создает управляемой блокировки на сервере 1С.Записать() параллельная транзакция успеет изменить те же самые данные, ваша транзакция перезапишет их некорректными вычислениями.БлокировкаДанных с исключительным режимом. В этом случае параллельные транзакции встанут в очередь ожидания еще до того, как попытаются изменить данные.Рассмотрим пример правильного применения явной блокировки при чтении и модификации:
НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);
// 1. Блокируем данные ДО чтения, чтобы гарантировать неизменность прочитанного
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.КакойТоРегистр");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Измерение1", Контрагент);
ЭлементБлокировки.УстановитьЗначение("Измерение2", Договор);
Блокировка.Заблокировать();
// 2. Читаем актуальное состояние
МенеджерЗаписи = РегистрыСведений.КакойТоРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Измерение1 = Контрагент;
МенеджерЗаписи.Измерение2 = Договор;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
// 3. Выполняем проверку или расчет
МенеджерЗаписи.Реквизит1 = "Обновленный комментарий";
МенеджерЗаписи.Записать(Истина);
КонецЕсли;
ЗафиксироватьТранзакцию();
Проанализируем опасение разработчиков относительно захвата более широкого диапазона данных, чем одна запись. Здесь важно разделять блокировки пространства 1С (управляемые блокировки) и физические блокировки в СУБД (например, MS SQL Server или PostgreSQL):
.1CD) любая операция записи блокирует всю физическую таблицу целиком. В клиент-серверном режиме этого не происходит.Разберем внутреннее устройство объекта РегистрСведенийМенеджерЗаписи. Этот объект разрабатывался преимущественно для интерактивной работы в интерфейсе пользователя. «Под капотом» платформа реализует работу менеджера записи через два последовательных набора записей: сначала стирается предыдущая запись, затем набором формируется новая.
Для высоконагруженных программных алгоритмов и пакетной обработки данных стандартом разработки 1С является использование объекта РегистрСведенийНаборЗаписей:
НаборЗаписей = РегистрыСведений.КакойТоРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Измерение1.Установить(Контрагент);
НаборЗаписей.Отбор.Измерение2.Установить(Договор);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Измерение1 = Контрагент;
НоваяЗапись.Измерение2 = Договор;
НоваяЗапись.Реквизит1 = "Какой-то комментарий";
// Запись набора с замещением заменяет строку одним прямым обращением к СУБД
НаборЗаписей.Записать(Истина);
Работа с набором записей снижает накладные расходы сервера приложений и прозрачнее взаимодействует с транзакционным механизмом СУБД.
В процессе обсуждения нередко высказывается предположение, что снятие флага «Основной отбор» у измерений регистра может устранить проблемы с блокировками. Это распространенное заблуждение.
Свойство «Основной отбор» не влияет на структуру физических индексов СУБД и транзакционные блокировки при записи. Оно управляет составом полей в таблицах регистрации изменений планов обмена (для распределенных информационных баз и синхронизации). Снятие этого флага не снижает гранулярность блокировок и не решает проблем с параллельной записью в регистр.