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

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

При оптимизации кода и переводе конфигурации на управляемый режим блокировок разработчики часто сталкиваются с вопросом: требуется ли перед модификацией независимого непериодического регистра сведений вручную создавать объект БлокировкаДанных, если запись выполняется через РегистрСведенийМенеджерЗаписи? Также возникает резонное опасение: не приведет ли вызов менеджера записи к блокировке соседних строк или всей таблицы целиком?

Разберем детально механизм работы управляемых блокировок платформы 1С:Предприятие при работе с записями регистров, проанализируем поведение СУБД и выясним, в каких сценариях явная блокировка действительно необходима, а где она является избыточной.

Автоматическое поведение платформы при вызове метода Записать()

Короткий и однозначный ответ на исходный вопрос: нет, при простой записи явная управляемая блокировка не нужна. Код с созданием объекта БлокировкаДанных непосредственно перед вызовом МенеджерЗаписи.Записать(Истина) выполняет абсолютно дублирующую работу.

Рассмотрим, как платформа 1С обрабатывает запись на уровне транзакции:

  1. В управляемом режиме блокировок платформа автоматически накладывает неявную исключительную управляемую блокировку по всем ключевым полям (измерениям регистра) в момент выполнения метода Записать().
  2. Объект РегистрСведенийМенеджерЗаписи всегда строго позиционирован на одну конкретную строку, состав которой однозначно определяется переданными значениями измерений (ключом записи).
  3. Таким образом, платформа самостоятельно блокирует в менеджере транзакционных блокировок ровно ту комбинацию измерений, которую вы записываете. Никаких дополнительных действий разработчика для защиты этой отдельной записи от параллельной записи из другой транзакции не требуется.

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


НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);

МенеджерЗаписи = РегистрыСведений.КакойТоРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Измерение1 = Контрагент;
МенеджерЗаписи.Измерение2 = Договор;
МенеджерЗаписи.Реквизит1  = "Какой-то комментарий";

// Явная блокировка здесь не требуется:
// вызов Записать() автоматически заблокирует запись по Измерение1 и Измерение2
МенеджерЗаписи.Записать(Истина);

ЗафиксироватьТранзакцию();

Когда явная блокировка через БлокировкаДанных все-таки необходима?

Выясним причину, по которой механизм БлокировкаДанных вообще существует в платформе. Явная установка исключительной блокировки требуется в классическом паттерне «ответственного чтения» (Read-Modify-Write).

Если перед выполнением записи вам необходимо сначала прочитать текущие данные регистра (или остатки/значения реквизитов), проверить бизнес-логику на основе этих данных и лишь затем записать новое значение, возникает уязвимость для состояния гонки (Race Condition):

  1. В управляемом режиме сеансы СУБД работают на уровне изоляции Read Committed (чтение зафиксированных данных). Обычный запрос или вызов метода Прочитать() у менеджера записи не удерживает блокировок на уровне СУБД и не создает управляемой блокировки на сервере 1С.
  2. Если между моментом прочтения данных вашей транзакцией и последующим вызовом Записать() параллельная транзакция успеет изменить те же самые данные, ваша транзакция перезапишет их некорректными вычислениями.
  3. Чтобы предотвратить параллельное изменение данных между чтением и записью, перед чтением вызывается БлокировкаДанных с исключительным режимом. В этом случае параллельные транзакции встанут в очередь ожидания еще до того, как попытаются изменить данные.

Рассмотрим пример правильного применения явной блокировки при чтении и модификации:


НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);

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

// 2. Читаем актуальное состояние
МенеджерЗаписи = РегистрыСведений.КакойТоРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Измерение1 = Контрагент;
МенеджерЗаписи.Измерение2 = Договор;
МенеджерЗаписи.Прочитать();

Если МенеджерЗаписи.Выбран() Тогда
    // 3. Выполняем проверку или расчет
    МенеджерЗаписи.Реквизит1 = "Обновленный комментарий";
    МенеджерЗаписи.Записать(Истина);
КонецЕсли;

ЗафиксироватьТранзакцию();

Может ли менеджер записи заблокировать всю таблицу или диапазон записей?

Проанализируем опасение разработчиков относительно захвата более широкого диапазона данных, чем одна запись. Здесь важно разделять блокировки пространства 1С (управляемые блокировки) и физические блокировки в СУБД (например, MS SQL Server или PostgreSQL):

  1. Управляемые блокировки 1С: работают исключительно на уровне сервера приложений 1С. Менеджер записи блокирует ровно одну запись по указанным измерениям. Всю таблицу управляемая блокировка захватить не может.
  2. Файловый режим работы: в файловой базе данных (формат .1CD) любая операция записи блокирует всю физическую таблицу целиком. В клиент-серверном режиме этого не происходит.
  3. Физические блокировки СУБД и порядок измерений: первичный ключ и кластерный индекс таблицы регистра формируются строго по порядку следования измерений в дереве метаданных. Если поиск или фильтрация затрагивают неполный индекс, СУБД может прибегнуть к сканированию диапазона страниц (Range Scan), временно накладывая постраничные блокировки. Для точечной записи по всем измерениям всегда используется уникальный точечный поиск.
  4. Эскалация блокировок СУБД: СУБД может повысить уровень строчных блокировок до всей таблицы только в случае превышения порога системных ресурсов (например, в MS SQL Server эскалация происходит при удержании более 5000 блокировок одной транзакцией). Запись одной строки через менеджер записи никогда не вызывает эскалацию блокировок СУБД.

Менеджер записи или Набор записей: что выбрать?

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

Для высоконагруженных программных алгоритмов и пакетной обработки данных стандартом разработки 1С является использование объекта РегистрСведенийНаборЗаписей:


НаборЗаписей = РегистрыСведений.КакойТоРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Измерение1.Установить(Контрагент);
НаборЗаписей.Отбор.Измерение2.Установить(Договор);

НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Измерение1 = Контрагент;
НоваяЗапись.Измерение2 = Договор;
НоваяЗапись.Реквизит1  = "Какой-то комментарий";

// Запись набора с замещением заменяет строку одним прямым обращением к СУБД
НаборЗаписей.Записать(Истина);

Работа с набором записей снижает накладные расходы сервера приложений и прозрачнее взаимодействует с транзакционным механизмом СУБД.

Влияет ли свойство измерения «Основной отбор» на блокировки?

В процессе обсуждения нередко высказывается предположение, что снятие флага «Основной отбор» у измерений регистра может устранить проблемы с блокировками. Это распространенное заблуждение.

Свойство «Основной отбор» не влияет на структуру физических индексов СУБД и транзакционные блокировки при записи. Оно управляет составом полей в таблицах регистрации изменений планов обмена (для распределенных информационных баз и синхронизации). Снятие этого флага не снижает гранулярность блокировок и не решает проблем с параллельной записью в регистр.

← На главную