Как проверить изменение объекта в базе данных перед записью, чтобы избежать ошибки несоответствия версий?

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

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

Понимание механизма «ВерсияДанных»

Для начала проанализируем, как платформа 1С отслеживает изменения. Каждый ссылочный объект (документ, справочник и др.) имеет системный реквизит ВерсияДанных. Технически в СУБД (например, MS SQL Server) это поле соответствует типу rowversion или timestamp. При каждой успешной записи объекта платформа автоматически обновляет это значение.

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

Способ 1: Прямое сравнение версий в коде

Рассмотрим самый простой и эффективный способ проверки, предложенный участниками обсуждения. Мы можем сравнить свойство ВерсияДанных текущего объекта со значением, которое хранится в базе данных на данный момент. Чтобы получить актуальное значение из базы, необходимо обратиться к реквизиту через ссылку.

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


// Проверяем, изменился ли объект в базе кем-то другим
Если ЭтотОбъект.ВерсияДанных <> ЭтотОбъект.Ссылка.ВерсияДанных Тогда
    Сообщить("Объект был изменен в другом сеансе! Рекомендуется обновить данные.");
    // Здесь можно либо вызвать исключение, либо перечитать объект
КонецЕсли;

Важный нюанс: Обращение через точку ЭтотОбъект.Ссылка.ВерсияДанных заставляет платформу выполнить отдельный SELECT запрос к базе данных. В рамках интерактивной работы в форме одного документа это оправдано, но при массовой обработке данных это может создать избыточную нагрузку.

Способ 2: Использование функций БСП для оптимизации

Для более профессионального подхода, особенно если вы работаете в современных типовых конфигурациях, рекомендуется использовать функции Библиотеки Стандартных Подсистем (БСП). Рассмотрим, как это сделать эффективнее с помощью общего модуля ОбщегоНазначения:


АктуальнаяВерсия = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ЭтотОбъект.Ссылка, "ВерсияДанных");

Если ЭтотОбъект.ВерсияДанных <> АктуальнаяВерсия Тогда
    // Обработка конфликта версий
КонецЕсли;

Этот метод предпочтительнее, так как он оптимизирован для получения только одного конкретного поля, не инициализируя чтение всего объекта целиком.

Анализ причин конфликта в типовых решениях (на примере УНФ)

Выясним причину, по которой такая ошибка может возникать «сама собой», даже если второй пользователь не трогал документ. Как показывает практика (и обсуждение в теме), часто виноват программный код внутри подписок на события или обработчиков ПередЗаписью / ПриЗаписи.

Рассмотрим ситуацию, возникшую в конфигурации 1С:Управление нашей фирмой (УНФ):

  1. При массовом проведении документов срабатывает типовой механизм (например, подсистема сообщений или мониторинга).
  2. Внутри транзакции записи основного документа код системы может попытаться записать какой-то связанный объект или даже вызвать повторную запись текущего объекта через ссылку.
  3. В результате версия объекта в базе данных обновляется «досрочно», и основной процесс записи, который инициировал транзакцию, видит несоответствие версий.

Если вы столкнулись с подобным при массовых операциях, проанализируйте стек вызовов. Иногда единственным решением является временное отключение проблемных подсистем (например, записи истории сообщений или логов), если они не критичны для текущей задачи.

Способ 3: Использование блокировок

Чтобы в принципе не допускать ситуации, когда проверка версии выдает «Ложь», мы можем использовать механизм пессимистических блокировок. Если мы программно блокируем объект в начале обработки, никто другой не сможет его изменить, пока мы не закончим.

Проанализируем пример использования метода Заблокировать():


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

Как поступить, если версии не совпали?

Если проверка показала, что БудетЖопа = (ЭтотОбъект.ВерсияДанных <> ЭтотОбъект.Ссылка.ВерсияДанных), у разработчика есть три пути:

  1. Отказ от записи: Выдать предупреждение пользователю и заставить его переоткрыть форму (метод Прочитать()).
  2. Принудительное перечитывание: Если изменения в памяти не критичны, можно программно вызвать ЭтотОбъект.Прочитать(), что обновит данные в памяти актуальными значениями из базы и синхронизирует версии.
  3. Программное слияние: Это самый сложный путь, когда мы считываем актуальный объект из БД в отдельную переменную, переносим в него только те реквизиты, которые изменил текущий процесс, и записываем его.

Рекомендации для массовой обработки данных

Если вы пишете обработку для перепроведения тысяч документов, постоянная проверка версии через Ссылка.ВерсияДанных в цикле сильно замедлит работу. В таких случаях эффективнее:

Таким образом, использование свойства ВерсияДанных — это мощный инструмент контроля целостности данных. Однако его использование должно быть осознанным, чтобы не превратить код в набор «костылей», скрывающих архитектурные ошибки в логике проведения или в типовых механизмах конфигурации.

← На главную