Как исправить ошибку «Данные были изменены или удалены другим пользователем» в 1С?

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

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

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

Выясним причину: Что такое оптимистическая блокировка?

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

  1. В каждой таблице базы данных (для справочников, документов и других ссылочных типов) существует специальное скрытое системное поле — ВерсияДанных. Это поле содержит уникальное значение (обычно это хэш или временная метка), которое автоматически меняется СУБД при абсолютно любой записи объекта в базу данных.
  2. Когда пользователь открывает форму документа на своем компьютере, платформа считывает текущее состояние объекта из СУБД вместе с его текущей версией данных (назовем ее Версия А) и кэширует её в контексте формы.
  3. Пока пользователь редактирует реквизиты на форме, в фоновом режиме происходит стороннее событие. Это может быть автоматический обмен данными, проведение подчиненного документа, работа фонового задания или действия другого пользователя. В результате этого внешнего процесса документ записывается непосредственно в СУБД. Версия данных в базе меняется и становится Версией Б.
  4. Пользователь нажимает на форму кнопку «Записать». В этот момент платформа инициирует транзакцию записи и первым делом сравнивает версию данных, сохраненную в форме (Версия А), с версией данных, которая сейчас реально хранится в таблице СУБД (Версия Б).
  5. Обнаружив несовпадение (Версия А не равна Версии Б), платформа понимает, что за время редактирования данные в базе успели устареть. Чтобы не перезаписать «вслепую» новые актуальные изменения старыми данными из формы пользователя, транзакция немедленно откатывается, и система выбрасывает исключение: «Данные были изменены или удалены другим пользователем».

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

Сценарий 1: Ошибка «самоблокировки» разработчиком (работа на сервере)

Очень часто разработчики сами наступают на эти грабли, когда программно изменяют и записывают тот же самый документ на сервере во время его интерактивного проведения или записи. Давайте проанализируем типичный пример некорректного кода.

Как делать НЕЛЬЗЯ:

Допустим, в обработчике формы ПриЗаписиНаСервере или в модуле объекта разработчик пишет следующий код:


// НЕПРАВИЛЬНО! Этот код вызовет ошибку оптимистической блокировки
ДокСсылка = ЭтотОбъект.Ссылка;
ПрограммныйОбъект = ДокСсылка.ПолучитьОбъект();
ПрограммныйОбъект.Комментарий = "Изменено программно";
ПрограммныйОбъект.Записать();

Что здесь произошло? Метод ПолучитьОбъект() считывает документ из базы, метод Записать() успешно сохраняет его в СУБД и обновляет ВерсиюДанных. Когда управление возвращается к основному процессу интерактивной записи формы, платформа обнаруживает, что версия данных формы не совпадает с только что обновленной версией в базе, и аварийно завершает транзакцию.

Как делать ПРАВИЛЬНО:

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

В модуле формы (в обработчиках типа ПередЗаписьюНаСервере) следует использовать связку преобразования данных:


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

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


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

Сценарий 2: Спасение данных пользователя («Умное слияние»)

Что делать, если документ действительно был изменен сторонним процессом (например, регламентным обменом данными с другой базой), пока пользователь весь день вносил изменения в форму? Простой вызов стандартного метода ЭтаФорма.Прочитать() полностью обновит форму из базы данных, но при этом безвозвратно затрет все введенные пользователем данные.

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

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

Посмотрим на пример программной реализации этого алгоритма:


&НаКлиенте
Процедура ЗаписатьПоДаннымФормы(Команда)
    
    // Шаг 1: Запрашиваем подтверждение у пользователя
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеПодтвержденияЗаписи", ЭтотОбъект);
    ПоказатьВопрос(ОписаниеОповещения, "Внимание! Документ был изменен в базе данных другим процессом. 
        |Хотите принудительно перезаписать документ вашими данными с формы?", РежимДиалогаВопрос.ДаНет);
        
КонецПроцедуры

&НаКлиенте
Процедура ПослеПодтвержденияЗаписи(РезультатВопроса, ДополнительныеПараметры) Экспорт
    
    Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
        ВыполнитьСпасительнуюЗаписьНаСервере();
    КонецЕсли;
    
КонецПроцедуры

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

Используя данный подход, вы можете дополнительно реализовать визуальное сравнение изменений: выводить пользователю отчет о том, какие именно реквизиты изменились в базе данных за время его работы, и предоставлять выбор — затереть их своими правками или объединить изменения вручную.

Сценарий 3: Предотвращение ошибок через механизм оповещений

Часто параллельные изменения происходят в рамках сессии одного и того же пользователя. Например, из формы документа пользователь открывает форму специальной обработки, которая программно модифицирует этот же документ в базе. Когда пользователь закрывает обработку и пытается продолжить работу с формой документа, он гарантированно получает ошибку блокировки.

Для предотвращения подобных ситуаций используйте глобальный механизм оповещений платформы 1С. Рассмотрим, как это работает:

  1. После того как обработка программно изменила и записала документ в базу данных, обязательно вызовите метод: ОповеститьОбИзменении(СсылкаНаДокумент);
  2. В форме самого документа добавьте предопределенный обработчик события ОбработкаОповещения. Платформа автоматически вызовет его при получении сигнала об изменении объекта:
    
    &НаКлиенте
    Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
        
        // Проверяем, что изменился именно наш открытый документ
        Если Источник = Объект.Ссылка Тогда
            // Проверяем, модифицировал ли пользователь форму
            Если Модифицированность Тогда
                ПоказатьПредупреждение(, "Этот документ был изменен в другой форме! Рекомендуется сохранить ваши данные.");
            Иначе
                // Если пользователь еще ничего не менял на форме, просто тихо обновляем ее данные
                ЭтаФорма.Прочитать();
            КонецЕсли;
        КонецЕсли;
        
    КонецПроцедуры
    

Благодаря этому подходу форма документа всегда будет оставаться в актуальном состоянии, предотвращая саму возможность возникновения конфликта версий при последующей записи.

Сценарий 4: Кардинальная оптимизация архитектуры (Регистры сведений)

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

Запомните важное правило проектирования систем на платформе 1С: любая перезапись документа ради изменения служебного реквизита приводит к сбросу версии его данных и увеличивает риск коллизий при интерактивной работе пользователей.

Решение проблемы:

Вынесите все часто меняющиеся технические реквизиты из метаданных документа в независимый регистр сведений. В качестве измерения этого регистра укажите ссылку на ваш документ, а в качестве ресурсов — необходимые статусы и даты.

При таком подходе фоновые задания, регламентные обмены и внешние интеграции будут записывать данные напрямую в этот регистр сведений по ключу документа. Запись в независимый регистр сведений никак не влияет на сам документ и не меняет его системное поле ВерсияДанных. Пользователь сможет спокойно редактировать и проводить открытый документ на форме, даже если в эту же секунду фоновый процесс обмена обновляет статус его выгрузки в ЕГАИС.

Резюме и рекомендации

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

← На главную