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