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