О стенде и инструментах
Тестовый стенд: режим управления блокировками управляемый, версия платформы 8.3.12.
Для расследования проблемы использовался технологический журнал (далее по тексту ТЖ).
Более подробно о нем написано написано в публикациях Рецепты приготовления технологического журнала и Описание почти всех событий технологического журнала.
Описание ошибки в ЖР
Расследуемая ошибка в журнале регистрации выглядит так:
Случай 1 (объектное чтение ПолучитьОбъект())
Воспроизведение
Для воспроизведения проблемы создадим множество потоков на запись следующим кодом
Процедура ЗапуститьФоновыеЗадания(Кнопка)
// Обработка сделана для расследования ошибки:
// Нарушение целостности чтения объекта базы данных из-за параллельного изменения объекта другим сеансом
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(Документ);
МассивПараметров.Добавить(1);
Для Инд = 1 по 100 Цикл
ФоновыеЗадания.Выполнить("Привилегированный.Записать", МассивПараметров, "КлючЗаписать " + Инд);
ФоновыеЗадания.Выполнить("Привилегированный.Записать", МассивПараметров, "КлючЗаписатьДублирующий " + Инд);
КонецЦикла;
КонецПроцедуры
Процедура Записать(Документ, Значение) Экспорт
ДокументОбъект = Документ.ПолучитьОбъект();
ДокументОбъект.Реквизит = Значение;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры
Расследование
Посмотрим на события в ТЖ:
Видим последовательность действий по документу ID=282:ae5600505699b66311e9899bd8fdecc8
- Наложения неявной блокировки платформой при записи (07:16.164118 TLOCK)
- Обновление в СУБД данных документа (07:19.321016 SDBL ... 'UPDATE ...')
- Фиксация транзакции вместе с этими изменениями (07:20 SDBL)
- Объектное чтение во время фиксации данных объекта (07:20.430016 SDBL, обращаем внимание на чтение версии по документу ID=282:ae5600505699b66311e9899bd8fdecc8)
- Ошибку чтения из-за параллельного изменения (07:20.430017 EXCP)
Исправление ошибки
Избежать ошибки можно организовав очередь к документу наложив явную управляемую блокировку перед записью документа
Процедура ЗаписатьСБлокировкой(Документ, Значение) Экспорт
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить();
ЭлементБлокировки.Область = "Документ.РеализацияТоваровУслуг";
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Ссылка", Документ);
Блокировка.Заблокировать();
ДокументОбъект = Документ.ПолучитьОбъект();
ДокументОбъект.Реквизит = Значение;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
ЗафиксироватьТранзакцию();
Исключение
ПодробнаяОшибка = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("Ошибка записи", УровеньЖурналаРегистрации.Ошибка,, ПодробнаяОшибка);
ОтменитьТранзакцию();
КонецПопытки;
КонецПроцедуры
В ТЖ будет видно 2 исхода события
1. Успешное наложение блокировки и ожидание 2 потоком пока 1 отработает
2. Неудачная попытка блокировки и откат транзакции
Случай 2 (объектное чтение Ссылка.Товары.НайтиСтроки())
Воспроизведение
Рассмотрим аналогичный случай при чтении табличной части документа
Процедура ЗапуститьФоновыеЗадания(Кнопка)
// Обработка сделана для расследования ошибки:
// Нарушение целостности чтения объекта базы данных из-за параллельного изменения объекта другим сеансом
Склад = Справочники.Склады.НайтиПоКоду("КодСклада");
МассивПараметровЗаписи = Новый Массив;
МассивПараметровЗаписи.Добавить(Документ);
МассивПараметровЗаписи.Добавить(1);
МассивПараметровЧтения = Новый Массив;
МассивПараметровЧтения.Добавить(Документ);
МассивПараметровЧтения.Добавить(Склад);
Для Инд = 1 по 100 Цикл
ФоновыеЗадания.Выполнить("Привилегированный.Записать", МассивПараметровЗаписи, "КлючЗаписать " + Инд);
ФоновыеЗадания.Выполнить("Привилегированный.Прочитать", МассивПараметровЧтения, "КлючПрочитать " + Инд);
КонецЦикла;
КонецПроцедуры
Процедура Записать(Ссылка, Значение) Экспорт
ДокументОбъект = Ссылка.ПолучитьОбъект();
ДокументОбъект.Значение= Значение;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры
Процедура Прочитать(Ссылка, Склад) Экспорт
Ссылка.Товары.НайтиСтроки(Новый Структура("Склад", Склад));
КонецПроцедуры
Расследование
Посмотрим на события в ТЖ:
Видим последовательность по документу ID=282:ae5600505699b66311e9899bd8fdecc8
- Наложения неявной блокировки платформой при записи (04:57.680428 TLOCK)
- Обновление в СУБД данных документа (04:57/996003 SDBL ... 'UPDATE ...')
- Фиксация транзакции вместе с этими изменениями (05:02 SDBL)
- Объектное чтение во время фиксации данных объекта (05:02.528150 SDBL, обращаем внимание на чтение версии по документу ID=282:ae5600505699b66311e9899bd8fdecc8)
- Ошибку чтения из-за параллельного изменения (05:02.528151)
Исправление ошибки
Избежать ошибки можно наложив явную управляемую блокировку перед записью документа или заменив объектное чтение чтением через запросы.
В ТЖ видно чтение через запросы и отсутствие ошибок
Заключение:
Ошибка связана с объектным чтением в момент принятия изменений платформой.
В зависимости от причины ошибки возможны два пути решения:
- организовать очередь используя управляемую блокировку (возможно повторение ошибки при доработках)
- переделать объектное чтение на чтение через запросы (повторение ошибки исключено)