При работе с механизмом управляемых блокировок в 1С начинающие (и даже опытные) разработчики часто сталкиваются с ситуацией, которая кажется нелогичной с точки зрения классических СУБД. Вы устанавливаете Исключительный режим блокировки на регистр, останавливаете выполнение кода точкой останова, открываете другое окно программы, выполняете запрос к этому же регистру — и данные успешно считываются. Возникает закономерный вопрос: почему блокировка «не работает»? В этой статье мы подробно проанализируем внутреннее устройство управляемых блокировок, выясним причины такого поведения и разберем правильные методы обеспечения целостности данных.
Для начала разберем ключевое отличие, которое часто сбивает с толку. В автоматическом режиме блокировок платформа 1С полностью полагалась на механизмы СУБД (MS SQL Server, PostgreSQL и др.). Когда вы блокировали таблицу, СУБД физически запрещала доступ к страницам данных или строкам для других транзакций. Это обеспечивало жесткую изоляцию, но приводило к серьезным проблемам с производительностью и параллельностью работы пользователей.
В режиме управляемых блокировок ситуация кардинально меняется. Рассмотрим основные тезисы:
Read Committed). Это позволяет СУБД разрешать чтение данных, даже если в другой транзакции эти данные сейчас изменяются.Посмотрим на код, который часто используют разработчики в модулях проведения документов (процедура ОбработкаПроведения):
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить();
ЭлементБлокировки.Область = "РегистрНакопления.ТоварыНаСкладах";
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
Когда этот код выполняется, сервер приложений помечает в своей памяти: «Регистр ТоварыНаСкладах заблокирован транзакцией №123 исключительно». Если в этот момент вы откроете консоль запросов и просто выполните ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах, платформа 1С отправит обычный SQL-запрос к базе данных. База данных, работая в режиме Read Committed, увидит, что данные доступны для чтения, и вернет их. Она не знает, что в памяти сервера приложений висит какая-то «управляемая блокировка».
Для того чтобы второй сеанс «увидел» блокировку первого сеанса, он также должен попытаться установить управляемую блокировку на ту же область данных. Выясним, как это реализовать на практике. Рассмотрим два основных сценария.
Если два документа пытаются одновременно списать остатки по одному и тому же складу, оба они в процедуре проведения вызывают объект БлокировкаДанных. В этом случае второй документ «зависнет» на методе Заблокировать() и будет ждать завершения транзакции первого документа. Здесь механизм работает идеально, так как оба участника соблюдают правила игры.
Самая частая ошибка — чтение данных запросом без предварительной установки блокировки. Чтобы чтение гарантированно дождалось окончания записи в другом сеансе, мы должны написать следующий код перед выполнением запроса:
// Создание объекта блокировка
Блокировка = Новый БлокировкаДанных;
// Добавление элемента блокировки
Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
// Установка режима блокировки
// Используем Исключительный, если планируем изменять,
// или Разделяемый, если только читаем для контроля
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
// Установка конкретных полей (гранулярность)
// Важно блокировать не весь регистр, а только нужные измерения
Элемент.УстановитьЗначение("Склад", СкладСсылка);
// Блокировка данных
Блокировка.Заблокировать();
// Только ПОСЛЕ этого выполняем запрос
Запрос = Новый Запрос("ВЫБРАТЬ ... ИЗ РегистрНакопления.ТоварыНаСкладах ГДЕ Склад = &Склад");
// ... выполнение запроса
Важно помнить, что управляемые блокировки работают только внутри транзакции. Если вы вызываете Блокировка.Заблокировать() вне транзакции, она будет снята мгновенно после выполнения этой строки кода, не оказав никакого влияния на последующие запросы.
Одним из главных преимуществ управляемого режима является возможность блокировать данные очень узко. Разберем пример, где мы блокируем не весь регистр, а только конкретный список номенклатуры на конкретном складе. Это позволяет другим пользователям одновременно проводить документы по другим складам или товарам, не мешая друг другу.
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
// Блокируем конкретный склад
Элемент.УстановитьЗначение("Склад", ВыбранныйСклад);
// Блокируем список номенклатуры из табличной части документа
Элемент.ИсточникДанных = СписокТоваров;
Элемент.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
В данном примере ИсточникДанных позволяет эффективно наложить блокировку на множество значений измерений одним вызовом. Это критически важно для высоконагруженных систем.
Проанализируем доводы в пользу такого решения, которые часто обсуждаются в профессиональном сообществе. Перенос логики блокировок на уровень сервера приложений позволил решить несколько фундаментальных проблем:
Подводя итог нашему разбору, сформулируем основные правила работы с блокировками в современных конфигурациях 1С:
УстановитьЗначение для измерений (Склад, Организация, Номенклатура).Понимание того, что управляемая блокировка — это не «замок на физической таблице», а «запись в журнале сервера приложений», поможет вам писать эффективный и надежный код, избегая ошибок нецелостности данных при конкурентном доступе.