При разработке механизмов автоматического изменения документов, особенно в регламентных заданиях или при интеграции с внешними системами через API, программисты 1С часто сталкиваются с неприятной ошибкой: "Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных". Эта ситуация возникает, когда один процесс (например, фоновое задание) считывает объект, долго выполняет внешние запросы, а в это время другой процесс или пользователь успевает изменить и записать этот же документ. В результате версия данных в памяти первого процесса становится неактуальной, и платформа блокирует запись.
Многие пытаются решить проблему вызовом метода Заблокировать(), но быстро обнаруживают, что он не помогает. Разберем подробно, почему так происходит, и какие инструменты платформы 1С версии 8.3 следует использовать для гарантированной блокировки документа от программных изменений.
Проанализируем ситуацию: метод объекта Заблокировать() реализует так называемую пессимистическую блокировку. Она предназначена в первую очередь для работы в интерактивном режиме (в интерфейсе). Когда пользователь открывает форму документа, система пытается его заблокировать, чтобы другой пользователь при попытке редактирования получил сообщение: "Объект уже заблокирован...".
Однако программная запись объекта через метод Записать() в другом сеансе (например, в регламентном задании) игнорирует пессимистические блокировки, если только в коде этого задания явно не прописана проверка блокировки. Более того, в клиент-серверном варианте метод Заблокировать() внутри фонового задания часто вообще не имеет смысла, так как нет визуального взаимодействия. Для решения нашей задачи нам необходим механизм управляемых блокировок.
Самый надежный способ гарантировать, что документ не будет изменен другими процессами во время вашей работы с ним — это использование объекта БлокировкаДанных строго внутри транзакции. Рассмотрим правильную последовательность действий:
Заблокировать() менеджера блокировок.Посмотрим на пример кода, который реализует этот алгоритм:
НачатьТранзакцию();
Попытка
// Создаем элемент блокировки
БлокировкаЗаписей = Новый БлокировкаДанных();
ЭлементБлокировки = БлокировкаЗаписей.Добавить("Документ.ЗаказПоставщику");
// Устанавливаем блокировку по конкретной ссылке
ЭлементБлокировки.УстановитьЗначение("Ссылка", СсылкаНаДокумент);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Блокируем данные в базе
БлокировкаЗаписей.Заблокировать();
// Теперь мы уверены, что никто не изменит документ, пока мы в транзакции
ЗаказОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Выполняем изменения
ЗаказОбъект.Комментарий = "Изменено регламентным заданием " + ТекущаяДата();
// Записываем объект
ЗаказОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Обработка ошибки: например, запись в журнал регистрации
ЗаписьЖурналаРегистрации("ПрограммнаяБлокировка", УровеньЖурналаРегистрации.Ошибка, , , КраткоеОписаниеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Важный момент: управляемая блокировка действует только до конца транзакции. Как только вы вызываете ЗафиксироватьТранзакцию() или ОтменитьТранзакцию(), блокировка снимается на уровне СУБД.
Выясним причину, по которой код из первого решения может быть опасен. Если ваше регламентное задание обращается к сайту через API (HTTP-запрос), время ожидания ответа может составлять несколько секунд или даже минут. Категорически не рекомендуется выполнять внешние запросы внутри транзакции!
Если вы заблокируете документ и "повиснете" на ожидании ответа от сайта, все остальные процессы (и пользователи), которые захотят записать или даже просто прочитать этот документ (в зависимости от режима блокировки), будут стоять в очереди. Это приведет к массовым таймаутам и параличу работы системы.
Разберем по шагам правильную архитектуру для таких случаев:
БлокировкаДанных и Записать(). Закрываем транзакцию.Проанализируем альтернативный подход, который часто оказывается более гибким. Вместо того чтобы менять сам документ и постоянно сталкиваться с конфликтами версий (ВерсияДанных), можно вынести всю служебную информацию (статусы обмена, ID заказов на сайте) в отдельный Регистр сведений.
Рассмотрим преимущества этого метода:
БлокировкаДанных, не затрагивая основной документ.Пример кода для блокировки записи в регистре перед изменением:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных();
Элемент = Блокировка.Добавить("РегистрСведений.СтатусыЗаказовНаСайте");
Элемент.УстановитьЗначение("Заказ", СсылкаНаДокумент);
Блокировка.Заблокировать();
// Чтение, логика и запись в регистр...
МенеджерЗаписи = РегистрыСведений.СтатусыЗаказовНаСайте.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Заказ = СсылкаНаДокумент;
МенеджерЗаписи.Статус = Перечисления.СтатусыОбмена.Выполнено;
МенеджерЗаписи.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
Подведем итог. Ошибка несоответствия версий — это защитный механизм 1С (оптимистическая блокировка), предотвращающий потерю данных при одновременном изменении. Чтобы эффективно управлять этим процессом, придерживайтесь следующих правил:
БлокировкаДанных) в режиме Исключительный внутри транзакции.Исключение при установке блокировки, так как система может выдать ошибку, если данные уже заблокированы другим сеансом. В этом случае регламентное задание должно просто пропустить объект и вернуться к нему в следующем цикле.Соблюдение этих принципов позволит создавать отказоустойчивые механизмы интеграции, которые не мешают работе пользователей и корректно обрабатывают конкурентный доступ к данным.