При работе в многопользовательской среде 1С:Предприятие одной из самых раздражающих проблем является ошибка «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи в базе данных». Чаще всего пользователь сталкивается с ней в момент нажатия кнопки «Записать» в форме справочника или документа. Ситуация осложняется тем, что визуально данные в базе никто не трогал, но платформа настойчиво отказывается сохранять изменения. Давайте подробно разберем природу этого механизма, выясним скрытые причины его срабатывания и научимся эффективно диагностировать такие коллизии.
Для начала проанализируем, почему вообще возникает это сообщение. В платформе 1С реализован механизм оптимистической блокировки. В отличие от пессимистической блокировки, которая «захватывает» объект сразу при открытии формы, оптимистическая позволяет множеству пользователей одновременно открывать один и тот же элемент на чтение и редактирование. Проверка происходит только в самый последний момент — при попытке записи данных в таблицу СУБД.
В каждой таблице объектов (справочников, документов) существует скрытое системное поле _Version (обычно это временная метка или инкрементное число). Когда мы открываем форму объекта, платформа считывает текущую версию. При нажатии кнопки «Записать» 1С сравнивает версию объекта, находящуюся в памяти формы, с той версией, которая записана в базе на данный момент. Если значения различаются, это означает, что кто-то другой (или какой-то процесс) успел изменить объект за то время, пока у нас была открыта форма. Запись отклоняется, чтобы не затереть чужие изменения «старыми» данными из формы пользователя.
Рассмотрим подробнее ситуации, когда вызов Записать() происходит неявно или из неожиданных мест, что и приводит к конфликту версий:
ПередЗаписью или ПриЗаписи, которая программно обращается к владельцу (номенклатуре) и обновляет в нем дату последнего изменения или служебный реквизит.ОбменДанными.Загрузка = Истина отключает прикладные проверки в коде (в модулях объекта), но не отключает проверку версии самой платформой. Любая запись объекта в этом режиме всё равно инкрементирует версию в БД.Если подозрение падает на фоновые процессы, нам необходимо научиться их отлаживать. Разберем по шагам, как настроить конфигуратор для перехвата управления в фоновом задании:
-debug в строке запуска службы).ПередЗаписью) или в модуле менеджера. Как только фоновое задание дойдет до этой строки, отладчик «поймает» его, и мы сможем просмотреть Стек вызовов, чтобы понять, какая именно процедура инициировала изменение.Бывают случаи, когда поймать момент записи отладчиком невозможно (например, на рабочем сервере под большой нагрузкой). Тогда на помощь приходит Технологический Журнал (ТЖ). Это самый мощный инструмент диагностики «невидимых» записей.
Настроим файл logcfg.xml так, чтобы он фиксировал все события записи в базу данных для конкретной таблицы. Нам нужно отслеживать события DBMSSQL (или DBV8DBEng для файловой базы). Пример настройки фильтра:
<event query="true">
<eq property="Name" value="DBMSSQL"/>
<like property="Sql" value="%REFERENCE123%"/>
</event>
Здесь REFERENCE123 — это имя таблицы справочника в SQL (его можно узнать через функцию ПолучитьСтруктуруХраненияБазыДанных()). ТЖ покажет нам не только факт записи, но и полный контекст на языке 1С, включая фоновое задание и пользователя.
Выясним, как программно обработать коллизию версий, если мы не можем исключить параллельную запись. Вместо того чтобы выдавать пользователю ошибку, мы можем применить стратегию «слияния» или повторной попытки.
Рассмотрим пример кода, который позволяет перечитать объект при возникновении ошибки версии:
Попытка
ОбъектНоменклатура.Записать();
Исключение
// Проверяем, не является ли ошибка конфликтом версий
Если ИнформацияОбОшибке().Описание Содержит "несоответствия версии" Тогда
// 1. Перечитываем актуальное состояние из базы
ОбъектНоменклатура.Прочитать();
// 2. Повторно заполняем важные поля, которые менял пользователь
// Здесь можно реализовать логику заполнения только измененных реквизитов
ОбъектНоменклатура.Наименование = "Новое наименование из формы";
// 3. Пытаемся записать снова
Попытка
ОбъектНоменклатура.Записать();
Исключение
Сообщить("Не удалось разрешить конфликт версий автоматически.");
КонецПопытки;
Иначе
ВызватьИсключение; // Если ошибка другая - пробрасываем выше
КонецЕсли;
КонецПопытки;
Разберем разницу между управляемыми блокировками и проверкой версии. Управляемая блокировка (БлокировкаДанных) используется для того, чтобы запретить другим сессиям начать процесс изменения, пока мы не закончили свой.
Если мы хотим, чтобы фоновый обмен не мешал пользователю, мы можем в процедуре обмена перед записью накладывать управляемую блокировку на элемент. Если в этот момент пользователь уже нажал «Записать» в форме, обмен подождет. Однако помните: блокировка работает, только если она установлена в обоих конфликтующих процессах. Если в одном месте используется Заблокировать(), а в другом — нет, платформа не увидит конфликта до момента финальной проверки версии в СУБД.
Подведем итог: чтобы победить ошибку несоответствия версии, необходимо сначала вычислить «невидимый» процесс записи с помощью отладки фоновых заданий или технологического журнала, а затем либо оптимизировать этот процесс (например, записывать объект только при наличии реальных изменений), либо реализовать алгоритм повторного чтения и записи объекта в коде.