Как правильно установить разделяемую блокировку на предмет бизнес-процесса в 1С ДО 2.1 для предотвращения изменений другими сеансами?

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

При работе с автоматизированными системами, такими как 1С:Предприятие, часто возникает необходимость обеспечить целостность данных в условиях многопользовательской работы. Представим ситуацию: вы программно создаете и запускаете бизнес-процесс, который оперирует неким "Предметом". В момент создания и старта бизнес-процесса, жизненно важно, чтобы этот "Предмет" не был изменен другим пользователем или фоновым заданием. Как этого добиться, используя механизм блокировок 1С?

В этой статье мы подробно рассмотрим, как правильно установить разделяемую блокировку на предмет бизнес-процесса в 1С:Документооборот 2.1, используя управляемый режим блокировок. Мы разберем, почему выбран именно разделяемый режим, где в коде должна находиться блокировка и как эффективно использовать объект БлокировкаДанных для обеспечения стабильной и корректной работы системы.

Понимание проблемы: Зачем нужна блокировка?

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

  1. Программно создается новый экземпляр бизнес-процесса.
  2. Он заполняется необходимыми данными, включая ссылку на свой "Предмет" (например, элемент справочника или документ).
  3. Бизнес-процесс записывается в базу данных.
  4. Затем он стартует, что может инициировать различные действия, в том числе чтение данных "Предмета".

Все эти действия выполняются в рамках одной транзакции. Наша ключевая задача — гарантировать, что "Предмет" бизнес-процесса останется неизменным с момента его прочтения для заполнения бизнес-процесса и до полного старта самого бизнес-процесса. Если другой сеанс одновременно попытается изменить "Предмет", это может привести к неконсистентности данных: бизнес-процесс стартует с одними данными "Предмета", а фактически в базе уже хранятся другие.

Для предотвращения такой ситуации мы и будем использовать разделяемую блокировку. Она позволит нам "заморозить" состояние "Предмета" на время выполнения нашей транзакции, не препятствуя при этом другим сеансам его просто просматривать.

Режим управляемых блокировок: Основы и важность

Прежде чем двигаться дальше, нам необходимо уточнить, что мы работаем в управляемом режиме блокировок. Это критически важный аспект, поскольку он диктует правила работы с блокировками в 1С.

В платформе 1С:Предприятие существует два основных режима управления блокировками:

  1. Автоматический режим: В этом режиме СУБД (система управления базами данных, например, PostgreSQL, MS SQL Server) самостоятельно управляет блокировками на основе операторов SQL, которые генерирует 1С. Разработчик напрямую не вмешивается в процесс блокирования. Для запросов, требующих блокировки на изменение, используется конструкция ДЛЯ ИЗМЕНЕНИЯ.
  2. Управляемый режим: В этом режиме разработчик берет на себя ответственность за явное управление блокировками с помощью специального объекта БлокировкаДанных. Этот режим дает гораздо большую гибкость и позволяет накладывать более "тонкие", гранулярные блокировки на логическом уровне объектов 1С (например, на конкретную запись справочника), а не на уровне таблиц СУБД. Это значительно повышает параллельность работы пользователей, так как не происходит избыточного блокирования больших объемов данных.

В нашем случае, как указано в сообщениях на форуме, используется именно управляемый режим. Это означает, что:

Понимание этих особенностей является фундаментальным для корректной реализации нашей задачи.

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

Объект БлокировкаДанных позволяет устанавливать блокировки в одном из двух основных режимов:

  1. РежимБлокировкиДанных.Разделяемый (Shared Lock): Этот режим позволяет нескольким транзакциям одновременно читать заблокированные данные. Однако, он запрещает любым другим транзакциям изменять эти данные, то есть устанавливать на них исключительную блокировку. Если транзакция уже установила разделяемую блокировку, другая транзакция, пытающаяся установить на те же данные исключительную блокировку, будет ожидать освобождения разделяемой блокировки.
  2. РежимБлокировкиДанных.Исключительный (Exclusive Lock): Этот режим является самым строгим. Он запрещает любым другим транзакциям не только изменять, но и читать заблокированные данные. Если транзакция установила исключительную блокировку, ни одна другая транзакция не сможет ни прочитать, ни изменить эти данные, пока исключительная блокировка не будет снята.

Теперь давайте проанализируем нашу задачу: мы хотим, чтобы "Предмет" не менялся, пока наш бизнес-процесс стартует. Но при этом мы не хотим полностью блокировать доступ к "Предмету" для других пользователей. Например, другой пользователь может захотеть просто открыть карточку "Предмета" для просмотра информации, не внося никаких изменений. В таком сценарии использование исключительной блокировки было бы излишне ограничительным и могло бы негативно сказаться на параллельности работы.

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

Оптимальное размещение блокировки в коде

После того как мы определились с режимом блокировки, нам нужно понять, где именно в нашей транзакции следует устанавливать блокировку. На форуме прозвучала важная рекомендация: "блокировку установите до начала транзакции" (сообщение 1), но затем это было скорректировано "хотя бы поднимите в начало транзакции" (сообщение 5) и подтверждено, что в управляемом режиме блокировки должны быть внутри транзакции (сообщение 3).

Правильный принцип гласит: блокируйте данные как можно раньше в транзакции. Почему это так важно?

  1. Предотвращение неповторяющегося чтения: Если мы сначала прочитаем "Предмет", а затем, спустя какое-то время, установим на него блокировку, то между чтением и блокировкой другой сеанс может успеть изменить данные. Это приведет к тому, что наша транзакция будет работать с устаревшими данными. Установка блокировки в самом начале гарантирует, что мы либо получим актуальные данные, либо дождемся их освобождения, и они останутся неизменными до конца нашей транзакции.
  2. Минимизация рисков: Чем дольше данные находятся в незаблокированном состоянии, тем выше вероятность коллизии. Ранняя блокировка снижает этот риск.

Таким образом, логическая последовательность действий в нашей транзакции будет следующей:

  1. Начать транзакцию.
  2. Установить необходимые блокировки на "Предмет".
  3. Выполнить все основные действия (создание, заполнение, запись бизнес-процесса).
  4. Стартовать бизнес-процесс.
  5. Зафиксировать транзакцию.

Теперь давайте посмотрим на пример кода, который демонстрирует правильное размещение и использование блокировки. Мы будем использовать исходный пример кода и интегрируем в него рекомендации.

Вот как будет выглядеть улучшенный код, где блокировка устанавливается в начале транзакции:


// Предполагаем, что НачалоТранзакции() уже было вызвано в предыдущей процедуре
// Если нет, то следует обернуть весь блок в НачатьТранзакцию() и Попытка/Исключение/КонецПопытки с ЗафиксироватьТранзакцию()/ОтменитьТранзакцию()
НачатьТранзакцию(); // Если транзакция не начата выше

Попытка

    // **1. Установка разделяемой блокировки на "Предмет" бизнес-процесса**
    // Это первое, что мы делаем после начала транзакции, чтобы гарантировать целостность данных "Предмета".
    
    // Создаем новый объект для управления блокировками
    БлокировкаДанных = Новый БлокировкаДанных();
    
    // Добавляем элемент блокировки. Пространство блокировки формируется из имени метаданных объекта.
    // Например, "Справочник.Номенклатура" или "Документ.ЗаказКлиента".
    ЭлементБлокировки = БлокировкаДанных.Добавить("Справочник." + Событие.Источник.Метаданные().Имя);
    
    // Устанавливаем режим блокировки: Разделяемый.
    // Это означает, что другие транзакции могут читать "Предмет", но не могут его изменять.
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Разделяемый;
    
    // Указываем конкретную ссылку на объект, который мы хотим заблокировать.
    // Это обеспечивает очень высокую гранулярность блокировки - блокируется только одна запись, а не вся таблица.
    ЭлементБлокировки.УстановитьЗначение("Ссылка", Событие.Источник);
    
    // Выполняем саму операцию блокировки.
    // Если "Предмет" уже заблокирован исключительной блокировкой другим сеансом,
    // текущий сеанс будет ожидать освобождения блокировки.
    БлокировкаДанных.Заблокировать();
    
    // **2. Выполнение основной бизнес-логики в рамках транзакции**
    // После успешной установки блокировки, мы можем безопасно выполнять операции
    // с "Предметом" и создавать бизнес-процесс, зная, что его состояние стабильно.
    
    // Создаем новый экземпляр бизнес-процесса
    БизнесПроцесс = БизнесПроцессы[Шаблон.ТипШаблона].СоздатьБизнесПроцесс();
    
    // Заполняем бизнес-процесс необходимыми данными, включая ссылку на "Предмет"
    БизнесПроцесс.Заполнить();
    
    // Записываем бизнес-процесс в базу данных
    БизнесПроцесс.Записать();
    
    // Стартуем бизнес-процесс
    СтартПроцессовСервер.СтартоватьПроцесс(БизнесПроцесс);
    
    // **3. Фиксация транзакции**
    // После успешного выполнения всех действий, фиксируем транзакцию.
    // При фиксации транзакции все установленные ею блокировки автоматически снимаются.
    ЗафиксироватьТранзакцию();
    
Исключение
    // **4. Отмена транзакции в случае ошибки**
    // Если в процессе выполнения возникла ошибка, мы должны отменить транзакцию.
    // При отмене транзакции все установленные ею блокировки также автоматически снимаются.
    ОтменитьТранзакцию();
    
    // Важно перевыбросить исключение, чтобы вышестоящий код мог его обработать.
    ВызватьИсключение; 

КонецПопытки;

В этом коде мы четко видим, что установка блокировки происходит сразу после начала транзакции. Это гарантирует, что к моменту создания и старта бизнес-процесса, "Предмет" будет защищен от изменений извне.

Особенности работы с пространством блокировок

Давайте подробнее рассмотрим, как работают строки:


ЭлементБлокировки = БлокировкаДанных.Добавить("Справочник." + Событие.Источник.Метаданные().Имя);
ЭлементБлокировки.УстановитьЗначение("Ссылка", Событие.Источник);

В управляемом режиме 1С, блокировки накладываются на так называемые пространства блокировок. Каждое пространство блокировок соответствует определенному объекту метаданных (например, справочнику, документу, регистру). Когда мы вызываем БлокировкаДанных.Добавить(), мы указываем имя пространства блокировок, на которое будет действовать наша блокировка. Для элементов справочников или документов, пространство блокировок обычно формируется как "Справочник.ИмяСправочника" или "Документ.ИмяДокумента".

Параметр Событие.Источник.Метаданные().Имя позволяет динамически получить имя метаданных того объекта, который является "Предметом" нашего бизнес-процесса. Например, если "Предмет" — это элемент справочника "Номенклатура", то строковое представление будет "Справочник.Номенклатура".

Метод ЭлементБлокировки.УстановитьЗначение("Ссылка", Событие.Источник) является крайне важным. Он позволяет нам наложить блокировку не на все пространство "Справочник.ИмяСправочника" (то есть, на всю таблицу справочника), а только на конкретную запись — ту, на которую указывает Событие.Источник. Это называется гранулярной блокировкой.

Использование гранулярных блокировок — это одно из ключевых преимуществ управляемого режима. Оно значительно повышает параллельность работы системы, поскольку не препятствует другим пользователям работать с другими записями того же справочника, пока мы блокируем только одну конкретную запись. Если бы мы не указали УстановитьЗначение("Ссылка", ...), блокировка могла бы быть наложена на все пространство, что привело бы к блокировке всей таблицы справочника и серьезно снизило бы производительность.

Для различных типов объектов могут быть доступны разные поля для УстановитьЗначение(). Например, для регистров это могут быть поля измерений. Однако, для большинства ссылочных объектов, таких как справочники и документы, блокировка по полю "Ссылка" является стандартным и наиболее эффективным способом блокировки конкретного элемента.

Использование запросов ДЛЯ ИЗМЕНЕНИЯ в управляемом режиме

Мы уже упоминали об этом ранее, но повторим, поскольку это частая ошибка, которая может привести к ложному чувству защищенности. В режиме управляемых блокировок, конструкция ДЛЯ ИЗМЕНЕНИЯ в запросах к базе данных не приводит к установке блокировок на уровне 1С. Она предназначена для автоматического режима блокировок, где СУБД самостоятельно управляет транзакциями.

Если в вашем коде используется управляемый режим блокировок и вы встретили запрос типа:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               |    Справочник.Номенклатура.Ссылка
               |ИЗ
               |    Справочник.Номенклатура КАК Справочник.Номенклатура
               |ГДЕ
               |    Справочник.Номенклатура.Ссылка = &Ссылка
               |ДЛЯ ИЗМЕНЕНИЯ";
Запрос.УстановитьПараметр("Ссылка", Событие.Источник);
Результат = Запрос.Выполнить().Выбрать();

Знайте, что строка ДЛЯ ИЗМЕНЕНИЯ будет проигнорирована платформой в части управляемых блокировок. Фактически она может оказать влияние только на запрос, который отправляется в СУБД, и тот может установить блокировку на уровне СУБД (например, Row Lock, Table Lock), если СУБД так решит, но это не будет управляться объектом БлокировкаДанных в контексте логики 1С. В управляемом режиме все явные блокировки должны быть установлены через объект БлокировкаДанных, как мы это рассмотрели в примере выше.

Поэтому, если ваш проект или конфигурация работает в управляемом режиме, всегда используйте объект БлокировкаДанных для контроля над блокировками, если вам нужна явная защита данных.

Заключение

Мы подробно разобрали процесс установки разделяемой блокировки на предмет бизнес-процесса в 1С:Предприятие в режиме управляемых блокировок. Мы выяснили, что:

  1. Блокировка должна быть установлена внутри транзакции.
  2. Для обеспечения максимальной целостности данных, блокировку следует устанавливать как можно раньше после начала транзакции.
  3. РежимБлокировкиДанных.Разделяемый является оптимальным выбором для нашей задачи, так как он защищает данные от изменений, но позволяет их просматривать.
  4. Использование объекта БлокировкаДанных с методами Добавить() и УстановитьЗначение("Ссылка", ...) обеспечивает гранулярную блокировку конкретной записи, что способствует высокой параллельности работы системы.
  5. В управляемом режиме блокировок конструкция ДЛЯ ИЗМЕНЕНИЯ в запросах 1С не имеет эффекта для управляемых блокировок, и все управление осуществляется через объект БлокировкаДанных.

Правильное использование транзакций и управляемых блокировок является фундаментальным аспектом при разработке высоконагруженных и многопользовательских систем на платформе 1С:Предприятие. Применяя эти рекомендации, вы сможете обеспечить надежность и консистентность данных ваших бизнес-процессов, избегая при этом излишних ограничений на работу пользователей. Всегда помните о балансе между целостностью данных и производительностью, и выбирайте режим блокировки, наиболее подходящий для вашей конкретной задачи.

← На главную