Управляемые блокировки на регистре сведений: почему паттерн read-modify-write опасен и как его исправить | infolimp.ru

Управляемые блокировки на регистре сведений: почему паттерн read-modify-write опасен и как его исправить

19 июня 2026 · infolimp.ru

Паттерн «прочитал → вычислил → записал» опасен на любом регистре сведений при конкурентном доступе нескольких сеансов к одному набору записей. Платформа 1С ставит неявные блокировки на оба типа — и на подчинённый регистратору, и на независимый. Проблема не в их отсутствии, а в том, что разделяемой блокировки при чтении недостаточно. Разбираем механизм, два сценария гонки и стандартный шаблон платформы.

Как работают неявные блокировки на регистрах сведений

Режим управляемых блокировок создавался для максимальной параллельности за счёт низких уровней изоляции в СУБД (Read Committed). Это не означает, что платформа снимает с себя ответственность за данные.

Платформа ставит неявные управляемые блокировки на оба типа регистров сведений:

Для регистра, подчинённого регистратору, платформа добавляет исключительную блокировку автоматически при проведении документа. Для независимого регистра этого автоматического шага нет — разработчик добавляет его сам. Сам же механизм неявных транзакционных блокировок работает одинаково для обоих типов.

Миф «у независимого регистра нет блокировок» — неверен. Блокировки есть. Проблема в другом: неявной разделяемой блокировки при чтении недостаточно для паттерна чтение→изменение.

Где возникает гонка: два сценария

Типовая задача: независимый регистр «Следующий номер партии», два сеанса конкурируют за запись.

Сценарий 1: чтение вне транзакции. Без НачатьТранзакцию() платформа расценивает чтение как «безответственное» — оно выполняется без оглядки на чужие блокировки. Оба сеанса читают «номер 5», оба пишут «номер 6». Потерянное обновление — без единого сообщения об ошибке.

Сценарий 2: чтение в транзакции без явной исключительной блокировки.

// Сеанс А:
НачатьТранзакцию();  // → читает «5», получает разделяемую блокировку

// Сеанс Б (параллельно):
НачатьТранзакцию();  // → читает «5», получает разделяемую блокировку

// Сеанс А пытается записать «6» → запрашивает исключительную → ждёт Б
// Сеанс Б пытается записать «6» → запрашивает исключительную → ждёт А
// → Взаимоблокировка (deadlock). Платформа выбросит исключение.

В первом сценарии данные молча повреждаются. Во втором — транзакция падает с ошибкой конфликта блокировок. Неявных блокировок в обоих случаях недостаточно.

Объект БлокировкаДанных: стандартный шаблон платформы

Явная исключительная блокировка до чтения — стандартный шаблон платформы для паттерна чтение→изменение. Пока один сеанс держит исключительную блокировку (в клиент-серверном режиме), второй не может ни прочитать с разделяемой блокировкой, ни записать тот же набор записей.

НачатьТранзакцию();
Попытка

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

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

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

Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Что делает каждая строка

Выбор режима: Исключительный или Разделяемый

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

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

Поглощение и эскалация блокировок

Два поведения платформы, которые важно знать при работе под нагрузкой.

Поглощение. Платформа склеивает пересекающиеся блокировки. Блокировка, в которой указаны значения не всех измерений, поглотит блокировку с большим числом указанных измерений — при условии совпадения значений общих полей. Если вы заблокировали весь «Склад», блокировка отдельной «Номенклатуры» на этом складе поглощается автоматически.

Эскалация. Жёсткое ограничение платформы: если на одно пространство накладывается более 100 000 блокировок, происходит эскалация — система блокирует пространство целиком. Если в этот момент возникает конфликт с уже наложенными чужими блокировками, эскалация отменяется. Практический вывод: не блокируйте в цикле по одной записи при больших объёмах — задавайте диапазон через УстановитьЗначение по общему измерению.

Три ошибки в реальных проектах

1. Блокировка после чтения. Схема «прочитал → заблокировал → записал» не работает: в промежутке между чтением и блокировкой другой сеанс уже мог прочитать то же значение. Правило одно: данные, которые будут перезаписаны, нужно читать уже после вызова Заблокировать().

2. Чтение вне транзакции. «Для чтения транзакция не нужна» — распространённое заблуждение. Вне транзакции чтение расценивается как безответственное и может вернуть промежуточное состояние. НачатьТранзакцию() обязателен до вызова Заблокировать().

3. Разделяемый режим там, где нужен Исключительный. Если сеанс читает с разделяемой блокировкой и затем пытается записать, а второй сеанс тоже держит разделяемую на те же записи — взаимоблокировка. При использовании явных управляемых блокировок платформы для паттерна чтение→изменение — только Исключительный.

Симптом в production: несколько документов подряд получили одинаковый «следующий номер» или одинаковую «следующую партию» — чтение выполнялось вне транзакции. Если документы падают с ошибкой конфликта блокировок при одновременном проведении — чтение было в транзакции, но без явной исключительной блокировки до чтения. Паттерн один, симптомы разные.

Правило для любого регистра сведений

Неявные блокировки платформы защищают от одновременной неконтролируемой записи. Для паттерна чтение→изменение их недостаточно — это справедливо для обоих типов регистров. Разница лишь в том, что для подчинённого при проведении документа платформа добавляет нужные блокировки сама. Для независимого — добавляет разработчик.

Если алгоритм считывает данные и затем изменяет их в той же транзакции — явная исключительная блокировка до чтения обязательна. Это идиоматическое решение платформы, одинаково применимое для обоих типов регистров.

Профессиональные решения для 1С и marketplace-интеграций — каталог отчётов и инструментов на витрине НОПи.

Перейти в каталог решений →