Возможен ли конфликт блокировок при программном изменении объекта 1С и как его избежать?

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

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

Анализ исходной ситуации

Проанализируем представленный код:


Процедура ИзменитьНаименованиеНоменклатуры(Кнопка)
    НачатьТранзакцию();
    НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
    НоменклатураОбъект.Наименование = НоменклатураОбъект.Наименование + " (изменения)";
    НоменклатураОбъект.Записать();
    ЗафиксироватьТранзакцию();
КонецПроцедуры

На первый взгляд, использование явной транзакции НачатьТранзакцию() должно защитить нас. Однако в режиме управляемых блокировок (который является стандартом для современных конфигураций) вызов ПолучитьОбъект() выполняет простое чтение данных из базы без установки каких-либо блокировок на уровне СУБД или платформы. Блокировка (исключительная) будет установлена системой автоматически только в момент выполнения метода Записать().

Конфликт блокировок vs Ошибка версии данных

Важно различать два типа проблем, которые могут возникнуть при выполнении этого кода в конкурентной среде:

  1. Несоответствие версий объекта: Это наиболее вероятный исход. Если между вызовами ПолучитьОбъект() и Записать() другой пользователь или фоновое задание успеет изменить этот же элемент справочника и зафиксировать транзакцию, то при попытке выполнения вашего метода Записать() система выдаст ошибку "Объект изменен или удален другим пользователем". Это происходит потому, что версия объекта в памяти вашего сеанса перестает соответствовать версии в базе данных.
  2. Конфликт блокировок (Lock Conflict): Эта ситуация возникнет, если другой сеанс уже вызвал метод Записать() для этого же объекта, но еще не завершил свою транзакцию. Ваш сеанс в этот момент "зависнет" на ожидании освобождения ресурса. Если ожидание превысит установленный лимит (обычно 20 секунд), будет выброшено исключение о конфликте блокировок.

Рассмотрим подробнее, как мы можем предотвратить эти ситуации и обеспечить корректное изменение данных.

Решение 1: Использование управляемых блокировок (Transaction Lock)

Чтобы исключить ситуацию, когда данные меняются "под носом" между чтением и записью, мы должны явно указать системе, что намерены изменять этот объект. Для этого используется объект БлокировкаДанных. Разберем по шагам правильный алгоритм:


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

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

// Устанавливаем блокировку ДО чтения данных
Блокировка.Заблокировать();

// Теперь чтение безопасно - никто не сможет изменить объект, пока мы в транзакции
НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
НоменклатураОбъект.Наименование = НоменклатураОбъект.Наименование + " (изменения)";
НоменклатураОбъект.Записать();

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

Почему это работает: Метод Заблокировать() сообщает менеджеру управляемых блокировок 1С, что данная ссылка зарезервирована нашей транзакцией в исключительного режиме. Любой другой процесс, попытавшийся прочитать этот объект с установкой блокировки или записать его, будет ждать завершения нашей транзакции. Таким образом, мы переводим работу из "оптимистичного" режима в "пессимистичный", гарантируя неизменность данных на протяжении всей операции.

Решение 2: Пессимистическая блокировка (Object Lock)

Иногда требуется защитить объект от изменения еще на этапе открытия формы пользователем. Для этого существует метод ЗаблокироватьДанныеДляРедактирования(). Проанализируем его особенности:

Рассмотрим пример использования объектной блокировки для предотвращения конфликтов при интерактивной работе:


Попытка
    // Блокируем для текущей формы или сеанса
    ЗаблокироватьДанныеДляРедактирования(НоменклатураСсылка);
Исключение
    Сообщить("Объект уже редактируется другим пользователем: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

Для надежного программного кода рекомендуется всегда комбинировать транзакции и управляемые блокировки (Решение 1).

Нужна ли явная транзакция для одного объекта?

В сообщении 5 на форуме прозвучало мнение, что транзакция не нужна для записи одного объекта, так как существует неявная транзакция. Выясним причину, почему это утверждение верно лишь отчасти. Неявная транзакция действительно открывается системой автоматически при вызове Записать(). Но если ваша задача — прочитать объект, выполнить сложные вычисления на основе его текущих данных и только потом записать, то без явной транзакции и управляемой блокировки вы рискуете записать неактуальные данные (если кто-то изменил их в промежутке).

Помните: Управляемая блокировка (БлокировкаДанных) живет только внутри транзакции. Если вы вызовете Блокировка.Заблокировать() вне явной транзакции, она будет снята мгновенно после выполнения команды, и смысла в ней не будет.

Выводы и рекомендации

Подведем итоги нашего анализа ситуации с конфликтами блокировок:

  1. Используйте управляемые блокировки (БлокировкаДанных) внутри явной транзакции, если вам критически важно, чтобы данные объекта не изменились в процессе выполнения вашего кода.
  2. Всегда устанавливайте блокировку ДО получения объекта через ПолучитьОбъект(). Если сделать это после, вы можете заблокировать уже измененную версию данных.
  3. Обрабатывайте исключения. Любая блокировка может привести к таймауту. Оборачивайте вызовы Заблокировать() и Записать() в конструкцию Попытка...Исключение, чтобы корректно информировать пользователя о занятости ресурса.
  4. Транзакция — это не только про групповую запись. Даже для одного объекта транзакция необходима, если требуется обеспечить атомарность процесса "Чтение - Изменение - Запись".

Таким образом, хотя в исходном коде конфликт блокировок возможен (в узком окне времени), гораздо более вероятна ошибка версии данных. Применение БлокировкаДанных решает обе эти проблемы, обеспечивая надежную работу системы в многопользовательском режиме.

← На главную