Как программно заблокировать документ в 1С для предотвращения ошибки несоответствия версий?

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

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

Многие пытаются решить проблему вызовом метода Заблокировать(), но быстро обнаруживают, что он не помогает. Разберем подробно, почему так происходит, и какие инструменты платформы 1С версии 8.3 следует использовать для гарантированной блокировки документа от программных изменений.

Почему стандартный метод Заблокировать() не работает в фоновых заданиях?

Проанализируем ситуацию: метод объекта Заблокировать() реализует так называемую пессимистическую блокировку. Она предназначена в первую очередь для работы в интерактивном режиме (в интерфейсе). Когда пользователь открывает форму документа, система пытается его заблокировать, чтобы другой пользователь при попытке редактирования получил сообщение: "Объект уже заблокирован...".

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

Решение 1: Использование управляемой блокировки внутри транзакции

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

  1. Открываем транзакцию.
  2. Создаем объект управляемой блокировки.
  3. Указываем пространство блокировки (наш документ) и конкретную ссылку.
  4. Вызываем метод Заблокировать() менеджера блокировок.
  5. Получаем объект документа, вносим изменения и записываем его.
  6. Фиксируем транзакцию.

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


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

Важный момент: управляемая блокировка действует только до конца транзакции. Как только вы вызываете ЗафиксироватьТранзакцию() или ОтменитьТранзакцию(), блокировка снимается на уровне СУБД.

Решение 2: Проблема длительных операций и внешних API

Выясним причину, по которой код из первого решения может быть опасен. Если ваше регламентное задание обращается к сайту через API (HTTP-запрос), время ожидания ответа может составлять несколько секунд или даже минут. Категорически не рекомендуется выполнять внешние запросы внутри транзакции!

Если вы заблокируете документ и "повиснете" на ожидании ответа от сайта, все остальные процессы (и пользователи), которые захотят записать или даже просто прочитать этот документ (в зависимости от режима блокировки), будут стоять в очереди. Это приведет к массовым таймаутам и параличу работы системы.

Разберем по шагам правильную архитектуру для таких случаев:

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

Решение 3: Использование регистра сведений как "семафора"

Проанализируем альтернативный подход, который часто оказывается более гибким. Вместо того чтобы менять сам документ и постоянно сталкиваться с конфликтами версий (ВерсияДанных), можно вынести всю служебную информацию (статусы обмена, ID заказов на сайте) в отдельный Регистр сведений.

Рассмотрим преимущества этого метода:

Пример кода для блокировки записи в регистре перед изменением:


НачатьТранзакцию();
Попытка
    Блокировка = Новый БлокировкаДанных();
    Элемент = Блокировка.Добавить("РегистрСведений.СтатусыЗаказовНаСайте");
    Элемент.УстановитьЗначение("Заказ", СсылкаНаДокумент);
    Блокировка.Заблокировать();
    
    // Чтение, логика и запись в регистр...
    МенеджерЗаписи = РегистрыСведений.СтатусыЗаказовНаСайте.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Заказ = СсылкаНаДокумент;
    МенеджерЗаписи.Статус = Перечисления.СтатусыОбмена.Выполнено;
    МенеджерЗаписи.Записать();
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
КонецПопытки;

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

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

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

Соблюдение этих принципов позволит создавать отказоустойчивые механизмы интеграции, которые не мешают работе пользователей и корректно обрабатывают конкурентный доступ к данным.

← На главную