Управляемые блокировки
Разбираем вопрос, на котором сыпется большинство джунов: почему проверки «хватает ли остатка» недостаточно, если рядом её же выполняет второй пользователь.
Управляемая блокировка — это блокировка, которую разработчик ставит на данные явно, из кода, объектом
БлокировкаДанных, чтобы на время транзакции никто другой не смог прочитать или изменить те же строки и сломать вашу проверку.
Формулировка вопроса на собеседовании обычно звучит так: «Документ списывает товар. Перед проведением вы проверяете, что остаток на складе не уйдёт в минус. Достаточно ли просто прочитать остаток запросом и сравнить с числом?» Правильный ответ — нет, и умение объяснить почему отделяет джуна от мидла.
Зачем это нужно на практике
Представьте: на складе лежит 1 штука товара. Два менеджера одновременно проводят расходные документы на эту же штуку. Оба читают остаток — оба видят «1» — оба решают, что провести можно, и оба записывают движение. В итоге вы продали одну штуку дважды и ушли в минус. Это классическая гонка (race condition): между «прочитали остаток» и «записали движение» другой сеанс успел вклиниться.
Управляемая блокировка закрывает этот промежуток. Первый сеанс блокирует строку остатка по этому товару и складу — второй сеанс на своей блокировке ждёт, пока первый закончит транзакцию. Когда второй наконец получает доступ, он перечитывает остаток, видит уже «0» и корректно отказывает в проведении.
Как это выглядит в коде
Порядок действий всегда один и тот же: начали транзакцию → поставили блокировку → и только теперь читаем актуальные данные → проверяем → пишем.
Процедура ОбработкаПроведения(Отказ, Режим)
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах.Остатки");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
Для Каждого СтрокаТовары Из Товары Цикл
Элемент.УстановитьЗначение("Номенклатура", СтрокаТовары.Номенклатура);
Элемент.УстановитьЗначение("Склад", Склад);
КонецЦикла;
Блокировка.Заблокировать();
// Только ПОСЛЕ блокировки читаем актуальные остатки
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиТовары.НоменклатураКоличествоОстаток КАК Остаток,
| ОстаткиТовары.Номенклатура КАК Номенклатура
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ОстаткиТовары";
Запрос.УстановитьПараметр("Склад", Склад);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.Остаток < 0 Тогда
Сообщить("Не хватает остатка по: " + Выборка.Номенклатура);
Отказ = Истина;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Здесь важен объект БлокировкаДанных. Метод Добавить задаёт пространство блокировки — обычно это виртуальная таблица регистра (...Остатки). УстановитьЗначение сужает блокировку до конкретных значений измерений (конкретный товар, конкретный склад), чтобы не блокировать весь регистр целиком. Заблокировать() ставит блокировку в базе.
Разделяемая и исключительная
У блокировки есть режим. Разделяемая (РежимБлокировкиДанных.Разделяемый) — «читаю, не мешайте менять». Несколько разделяемых блокировок на одни данные уживаются, но исключительную рядом не пустят. Исключительная (Исключительный) — «я меняю, никого не пущу ни читать под блокировкой, ни писать». Перед проверкой-и-записью остатка берут именно исключительную: вы собираетесь менять данные, и параллельный доступ к ним недопустим.
Как это работает под капотом
С версии 8.1 у 1С два режима блокировок, и это ровно то, что спрашивают дальше.
| Автоматические блокировки | Управляемые блокировки |
| Ставит СУБД сама, на уровне таблиц/страниц | Ставит разработчик из кода, точечно по значениям |
Уровень изоляции высокий (часто SERIALIZABLE) | Уровень изоляции ниже (READ COMMITTED) |
| Блокирует «широко» — легко ловит лишние конфликты | Блокирует «узко» — меньше конфликтов, выше параллельность |
| Разработчику проще, но система «встаёт» под нагрузкой | Требует явных блокировок в коде, зато масштабируется |
Ключевая мысль для ответа: в управляемом режиме СУБД больше не защищает вас автоматически от гонок при чтении — уровень изоляции понижен ради параллельности. Поэтому логическую целостность (не уйти в минус) теперь обязан обеспечить разработчик, расставив управляемые блокировки руками. Автоматический режим делал это за вас, но ценой того, что база задыхалась на десятках одновременных пользователей. Современные конфигурации почти всегда работают в управляемом режиме.
Как отвечать на собеседовании
Скелет сильного ответа: «Просто прочитать остаток мало — между чтением и записью вклинится параллельный сеанс, будет гонка и минусовой остаток. Нужно внутри транзакции поставить управляемую блокировку объектом БлокировкаДанных на строки регистра остатков по нужным номенклатуре и складу в исключительном режиме, и только после блокировки читать остаток и проверять. Тогда второй сеанс подождёт нашу транзакцию и увидит уже актуальные данные». Если попросят про режимы — объясните разницу автоматических и управляемых блокировок и почему управляемый режим понизил изоляцию ради параллельности.
Частые ошибки на собеседовании
- «Достаточно проверить остаток запросом». Самый частый провал — не увидеть гонку между чтением и записью.
- Читать данные до блокировки. Если сначала прочитали остаток, а потом заблокировали — вы защитили запись, но проверяли устаревшее значение. Порядок строгий: блокировка → чтение → проверка.
- Блокировать весь регистр. Забыть
УстановитьЗначениеи заблокировать всю таблицу остатков — параллельность падает до нуля, все стоят в очереди. Блокируйте точечно по измерениям. - Путать блокировку и транзакцию. Блокировка живёт только внутри транзакции и снимается при её завершении. Управляемая блокировка вне транзакции смысла не имеет.
- Считать, что управляемая блокировка = блокировка СУБД. Управляемые блокировки 1С живут в собственном менеджере блокировок платформы, а не только в СУБД — это отдельный слой, который платформа согласует с базой.
Итоги-шпаргалка
- Проверка остатка без блокировки = гонка = минус на складе.
- Порядок неизменен: транзакция → БлокировкаДанных → чтение → проверка → запись.
Добавитьзадаёт таблицу,УстановитьЗначениесужает по измерениям,Заблокироватьставит блокировку.- Режимы: Разделяемый — для чтения, Исключительный — перед изменением.
- Автоматический режим защищал сам, но «широко» и медленно; управляемый — точечно и быстро, но целостность на разработчике.