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