В процессе разработки в системе 1С:Предприятие 8 часто возникает ситуация, когда при попытке выполнить метод Записать() система выдает критическую ошибку: «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных». Эта ошибка является следствием механизма оптимистической блокировки, который защищает данные от одновременного изменения разными пользователями или процессами. Разберем подробно, почему это происходит и как мы можем программно проверить состояние объекта перед его сохранением в базу данных.
Для начала проанализируем, как платформа 1С отслеживает изменения. Каждый ссылочный объект (документ, справочник и др.) имеет системный реквизит ВерсияДанных. Технически в СУБД (например, MS SQL Server) это поле соответствует типу rowversion или timestamp. При каждой успешной записи объекта платформа автоматически обновляет это значение.
Когда мы считываем объект в память (переменная ЭтотОбъект), мы получаем и его текущую версию. Если за то время, пока мы меняли реквизиты в коде или в форме, кто-то другой (другой пользователь или фоновое задание) успел записать этот же объект в базу, версия в базе изменится. В момент нашего вызова Записать() платформа сравнивает версию в памяти и версию в БД. Если они не совпадают — возникает исключение.
Рассмотрим самый простой и эффективный способ проверки, предложенный участниками обсуждения. Мы можем сравнить свойство ВерсияДанных текущего объекта со значением, которое хранится в базе данных на данный момент. Чтобы получить актуальное значение из базы, необходимо обратиться к реквизиту через ссылку.
Посмотрим на пример кода, который можно использовать перед вызовом записи:
// Проверяем, изменился ли объект в базе кем-то другим
Если ЭтотОбъект.ВерсияДанных <> ЭтотОбъект.Ссылка.ВерсияДанных Тогда
Сообщить("Объект был изменен в другом сеансе! Рекомендуется обновить данные.");
// Здесь можно либо вызвать исключение, либо перечитать объект
КонецЕсли;
Важный нюанс: Обращение через точку ЭтотОбъект.Ссылка.ВерсияДанных заставляет платформу выполнить отдельный SELECT запрос к базе данных. В рамках интерактивной работы в форме одного документа это оправдано, но при массовой обработке данных это может создать избыточную нагрузку.
Для более профессионального подхода, особенно если вы работаете в современных типовых конфигурациях, рекомендуется использовать функции Библиотеки Стандартных Подсистем (БСП). Рассмотрим, как это сделать эффективнее с помощью общего модуля ОбщегоНазначения:
АктуальнаяВерсия = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ЭтотОбъект.Ссылка, "ВерсияДанных");
Если ЭтотОбъект.ВерсияДанных <> АктуальнаяВерсия Тогда
// Обработка конфликта версий
КонецЕсли;
Этот метод предпочтительнее, так как он оптимизирован для получения только одного конкретного поля, не инициализируя чтение всего объекта целиком.
Выясним причину, по которой такая ошибка может возникать «сама собой», даже если второй пользователь не трогал документ. Как показывает практика (и обсуждение в теме), часто виноват программный код внутри подписок на события или обработчиков ПередЗаписью / ПриЗаписи.
Рассмотрим ситуацию, возникшую в конфигурации 1С:Управление нашей фирмой (УНФ):
Если вы столкнулись с подобным при массовых операциях, проанализируйте стек вызовов. Иногда единственным решением является временное отключение проблемных подсистем (например, записи истории сообщений или логов), если они не критичны для текущей задачи.
Чтобы в принципе не допускать ситуации, когда проверка версии выдает «Ложь», мы можем использовать механизм пессимистических блокировок. Если мы программно блокируем объект в начале обработки, никто другой не сможет его изменить, пока мы не закончим.
Проанализируем пример использования метода Заблокировать():
Попытка
ЭтотОбъект.Заблокировать();
// Теперь мы уверены, что версия в базе не изменится до момента записи
ЭтотОбъект.Записать();
Исключение
// Сюда попадем, если объект уже кем-то заблокирован
Сообщить("Не удалось заблокировать объект: " + ОписаниеОшибки());
КонецПопытки;
Если проверка показала, что БудетЖопа = (ЭтотОбъект.ВерсияДанных <> ЭтотОбъект.Ссылка.ВерсияДанных), у разработчика есть три пути:
Прочитать()).ЭтотОбъект.Прочитать(), что обновит данные в памяти актуальными значениями из базы и синхронизирует версии.Если вы пишете обработку для перепроведения тысяч документов, постоянная проверка версии через Ссылка.ВерсияДанных в цикле сильно замедлит работу. В таких случаях эффективнее:
ПолучитьОбъект()) и его записью (Записать()). Чем меньше этот интервал, тем ниже вероятность конфликта версий.Таким образом, использование свойства ВерсияДанных — это мощный инструмент контроля целостности данных. Однако его использование должно быть осознанным, чтобы не превратить код в набор «костылей», скрывающих архитектурные ошибки в логике проведения или в типовых механизмах конфигурации.