Використання транзакцій під час читання даних
1.1. Якщо читання даних з інформаційної бази має бути відповідальним, таке читання слід виконувати в транзакції з попереднім установленням керованих блокувань. Загалом відповідальним слід вважати будь-яке читання, на основі результатів якого виконуються будь-які зміни в інформаційній базі або приймаються рішення.Наприклад, відповідальне читання даних потрібне в таких випадках:Використання читання поза транзакцією. Див. також
Використання читання поза транзакцією
1.1. Якщо читання даних з інформаційної бази має бути відповідальним, таке читання слід виконувати в транзакції з попереднім установленням керованих блокувань. Загалом відповідальним слід вважати будь-яке читання, на основі результатів якого виконуються будь-які зміни в інформаційній базі або приймаються рішення.
Наприклад, відповідальне читання даних потрібне в таких випадках:
-
Читання даних під час проведення, для подальшого формування рухів;
-
Читання даних для подальшого цілісного передавання в іншу систему, наприклад у програми типу «Клієнт-банк»;
-
Виконання групової обробки об'єктів *
* Примітка: перед модифікацією посилальних об'єктів зазвичай слід установлювати на них песимістичні об'єктні блокування.
Неправильно:
// 1. Прочитати регістр відомостей Запрос = Новый Запрос( "ВЫБРАТЬ РАЗРЕШЕННЫЕ | ЗаметкиПоПредмету.КоличествоЗаметок КАК КоличествоЗаметок |ИЗ | РегистрСведений.ЗаметкиПоПредмету КАК ЗаметкиПоПредмету |ГДЕ | ЗаметкиПоПредмету.Предмет = &Предмет"); Запрос.УстановитьПараметр("Предмет", ПредметЗаметок); Выборка = Запрос.Выполнить().Выбрать(); КоличествоЗаметок = 0; Если Выборка.Следующий() Тогда КоличествоЗаметок = Выборка.КоличествоЗаметок; КонецЕсли; // 2. Записати до регістру відомостей НаборЗаписей = РегистрыСведений.ЗаметкиПоПредмету.СоздатьНаборЗаписей(); НаборЗаписей.Отбор.Предмет.Установить(ПредметЗаметок); НоваяЗапись = НаборЗаписей.Добавить(); НоваяЗапись.Предмет = ПредметЗаметок; НоваяЗапись.КоличествоЗаметок = КоличествоЗаметок + 1; НаборЗаписей.Записать();
Правильно:
// 1. Почати транзакцію для пакета з двох операцій читання та запису регістру НачатьТранзакцию(); Попытка // 2. Установити виключне блокування на потрібний діапазон записів регістру, // щоб гарантувати, що в момент запису кількість нотаток не змінилася від моменту читання в якомусь іншому сеансі. БлокировкаДанных = Новый БлокировкаДанных; ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("РегистрСведений.ЗаметкиПоПредмету"); ЭлементБлокировкиДанных.УстановитьЗначение("Предмет", ПредметЗаметок); ЭлементБлокировкиДанных.Режим = РежимБлокировкиДанных.Исключительный; БлокировкаДанных.Заблокировать(); // 3. Прочитати регістр відомостей Запрос = Новый Запрос( "ВЫБРАТЬ РАЗРЕШЕННЫЕ | ЗаметкиПоПредмету.КоличествоЗаметок КАК КоличествоЗаметок |ИЗ | РегистрСведений.ЗаметкиПоПредмету КАК ЗаметкиПоПредмету |ГДЕ | ЗаметкиПоПредмету.Предмет = &Предмет"); Запрос.УстановитьПараметр("Предмет", ПредметЗаметок); Выборка = Запрос.Выполнить().Выбрать(); КоличествоЗаметок = 0; Если Выборка.Следующий() Тогда КоличествоЗаметок = Выборка.КоличествоЗаметок; КонецЕсли; // 4. Записати до регістру відомостей НаборЗаписей = РегистрыСведений.ЗаметкиПоПредмету.СоздатьНаборЗаписей(); НаборЗаписей.Отбор.Предмет.Установить(ПредметЗаметок); НоваяЗапись = НаборЗаписей.Добавить(); НоваяЗапись.Предмет = ПредметЗаметок; НоваяЗапись.КоличествоЗаметок = КоличествоЗаметок + 1; НаборЗаписей.Записать(); ЗафиксироватьТранзакцию(); Исключение // 5. Якщо під час установлення блокування виникла виняткова ситуація через те, що регістр уже заблоковано в іншому сеансі (або з інших причин), // скасувати транзакцію та записати відомості про помилку до журналу реєстрації. ОтменитьТранзакцию(); ЗаписьЖурналаРегистрации(НСтр("ru = 'Заметки'"), УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())); ВызватьИсключение; КонецПопытки;
У деяких випадках відповідальне читання не потрібне через розв'язуване прикладне завдання, наприклад:
- Отримання даних динамічними списками;
- Пошук даних;
- Формування більшості звітів.
У деяких випадках відповідальне читання не потрібне, оскільки конкурентна робота з даними малоймовірна або повністю виключена, наприклад:
-
Звернення до умовно постійної інформації. Наприклад, читання константи ВалютаРегламентированногоУчета або звернення до облікової політики;
-
Дії, які гарантовано виконуються в монопольному режимі. Наприклад, у процедурах оновлення та початкового заповнення даних інформаційної бази;
-
Дії над даними, доступ до яких має лише один користувач, тому конкурентна робота з ними малоймовірна або повністю виключена.
Наприклад, персональні дані, що зберігаються в «розрізі» користувачів.
1.2. У більшості випадків під час виконання читання в обробниках подій, пов'язаних із модифікацією даних, увесь код обробника виконується в межах системної транзакції, відкритої платформою, і явно відкривати нову транзакцію не потрібно.
Наприклад, у системній транзакції виконуються обробники модулів об'єктів і відповідні їм підписки на події:
-
ПередЗаписью;
-
ПриЗаписи;
-
ПередУдалением.
Докладніше — див. документацію до платформи 1С:Підприємство.
1.3. Якщо в транзакції виконується відповідальне читання даних без їх подальшої зміни, необхідно встановити спільне кероване блокування (до виконання читання). Якщо ж прочитані дані надалі модифікуються, слід використовувати виключне блокування, інакше можливе виникнення взаємоблокування.
Приклад установлення спільного блокування (без відкриття транзакції — за припущенням, що раніше вже було відкрито системну транзакцію):
// 1. Установити спільне блокування для відповідального читання кількох пов'язаних об'єктів Блокировка = Новый БлокировкаДанных; ЭлементБлокировки = Блокировка.Добавить("Справочник.Приказы"); ЭлементБлокировки.УстановитьЗначение("Ссылка", ПриказСсылка); ЭлементБлокировки.Режим = РежимБлокировкиДанных.Разделяемый; Блокировка.Заблокировать(); // 2. Прочитати перший об'єкт — наказ ПриказОбъект = ПриказСсылка.ПолучитьОбъект(); // 3. Прочитати другий об'єкт — користувача (автора наказу) АвторПриказа = ПриказОбъект.Автор.ПолучитьОбъект();
Приклад установлення виключного блокування (без відкриття транзакції — за припущенням, що раніше вже було відкрито системну транзакцію):
// 1. Установити виключне блокування для відповідального читання об'єкта з метою його подальшої зміни Блокировка = Новый БлокировкаДанных; ЭлементБлокировки = Блокировка.Добавить("Справочник.Приказы"); ЭлементБлокировки.УстановитьЗначение("Ссылка", ПриказСсылка); ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный; // Можна не вказувати, оскільки за замовчуванням Виключний Блокировка.Заблокировать(); // 2. Отримати об'єкт для його подальшої модифікації Объект = ПриказСсылка.ПолучитьОбъект(); // Виконати блокування об'єкта від зміни іншими режимами або користувачами Объект.Заблокировать(); Объект.Реквизит = ... // 3. Записати змінений об'єкт Объект.Записать();
Використання читання поза транзакцією
2.1. Не слід прив'язувати логіку механізмів до ефекту брудного читання (можливості читання даних незафіксованих транзакцій), оскільки його немає у версійних СУБД (PostgreSQL, Oracle database), а прикладні рішення мають бути працездатними на всіх СУБД, що підтримуються платформою. Наприклад, якщо фонове завдання працює в транзакції, не слід намагатися відстежувати стан цього завдання за змінами в інформаційній базі — це не працюватиме на версійних СУБД.
2.2. У рідкісних випадках може бути прийнято рішення про виконання читання поза транзакцією. Наприклад, з міркувань підвищення паралельності в багатокористувацькій роботі. *
* Примітка: На СУБД MS SQL спроба читання поза транзакцією може завершитися помилкою "Could not continue scan with NOLOCK due to data movement". Зазвичай у разі виникнення такої помилки слід спробувати повторно виконати читання. При цьому кількість спроб і спосіб обробки помилки — окремий предмет проєктування в кожному конкретному випадку.
Див. також
-
Загальні відомості про надлишкові блокування
-
Обробка винятків під час використання транзакцій
-
Блокувальне читання залишків на початку транзакції