#std783¶
Транзакции: правила использования¶
Транзакция - это действие, которое может быть выполнено только целиком. Если часть действия не выполнена - все действие отменяется.
1.¶
Ошибка В этой транзакции уже происходили ошибки
возникает из-за несоблюдения требований к написанию кода. Такие ошибки сложно воспроизводить и отлаживать их исправление.
Особенности работы с транзакциями в платформе:
- Не поддерживаются вложенные транзакции (Вложенность транзакций).
- Если в транзакции возникло исключение транзакция может не завершится, даже если перехватить это исключение Ошибки базы данных и транзакции, Особенности работы объектов при отмене транзакции.
- Платформа может начинать транзакции сама. Это называется неявные транзакции. Глава 9.2. Механизм транзакций.
1.1.¶
Исключение не отменяет транзакцию. Оно взводит служебный флажок транзакции Запрет успешного завершения
. Реальная отмена произойдет тогда, когда код дойдет до функции ЗафиксироватьТранзакцию
или ОтменитьТранзакцию
.
Для каждого метода НачатьТранзакцию
должны быть парные методы ЗафиксироватьТранзакцию
и ОтменитьТранзакцию
.
В стандарте не указано
О том что такое флажки транзакции и как работает платформа смотрите в:
- Профессиональная разработка в системе 1С Предприятие, Том 1, стр 107-108
- Ошибки базы данных и транзакции.
- Глава 9.2.3. Вложенный вызов транзакций
- YouTube: Транзакции и блокировки: как их готовить программисту 1С?
1.2.¶
Начало транзакции и ее завершение (фиксацию) выполняйте в одном методе. Не должно быть разнесения начала и конца на несколько функций. Тот, кто будет расследовать ошибку, скажет спасибо!
Неправильно
Процедура ЗаписатьДанныеВИБ()
НачатьТранзакцию();
ЗаписатьДокумент();
КонецПроцедуры;
Процедура ЗаписатьДокумент()
Попытка
... // чтение или запись данных
ДокументОбъект.Записать()
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
... // дополнительные действия по обработке исключения
КонецПопытки;
КонецПроцедуры
Правильно
1.3.¶
Используйте следующий паттерн для работы с транзакциями:
Паттерн
НачатьТранзакцию(); // (1)
Попытка
БлокировкаДанных = Новый БлокировкаДанных;
ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("Документ.ПриходнаяНакладная");
ЭлементБлокировкиДанных.УстановитьЗначение("Ссылка", СсылкаДляОбработки);
ЭлементБлокировкиДанных.Режим = РежимБлокировкиДанных.Исключительный;
БлокировкаДанных.Заблокировать();
... // чтение или запись данных (2)
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию(); // (3)
Исключение
ОтменитьТранзакцию(); // (4)
ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"), // (5)
УровеньЖурналаРегистрации.Ошибка,
,
,
ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // (6)
КонецПопытки;
-
Метод
НачатьТранзакцию
должен быть вне блокаПопытка-Исключение
перед операторомПопытка
и ничего между ними; -
Все действия, выполняемые после метода
НачатьТранзакцию
, должны находиться в одном блокеПопытка
, в том числе чтение, блокировка и обработка данных; -
Метод
ЗафиксироватьТранзакцию
должен идти последним в блокеПопытка
перед операторомИсключение
, чтобы гарантировать, что послеЗафиксироватьТранзакцию
не возникнет исключение; -
До обработки исключений в блоке
Исключение
сначала вызовите методОтменитьТранзакцию
, а затем выполняйте другие действия, если они требуются; -
В блоке
Исключение
делайте запись в журнал регистрации; -
В конце блока
Исключение
добавляйте операторВызватьИсключение
. В противном случае исключение не будет передано выше по стеку вызовов, там не сработает обработка исключения, внешняя транзакция не будет отменена и платформа вызовет исключениеВ данной транзакции происходила ошибка
.
1.4.¶
Если можно избежать использования вложенных транзакций - постарайтесь избежать.
1.4.1.¶
Не вызывайте НачатьТранзакцию
если кроме записи объекта других действий не требуется. Платформа сделает все неявно сама, а читать код станет сложнее.
Избегайте открытия транзакции, если не требуется ответственное чтение данных #std648. Например, при записи нового объекта или набора записей регистра обычно ответственное чтение не требуется.
При вызове методов ПолучитьОбъект
(или Прочитать
для наборов записей) анализируйте должно ли происходить чтение ответственно. Если не должно - не вызывайте НачатьТранзакцию
.
Неправильно
Правильно
1.4.2.¶
Если вы разрабатываете метод, который предназначен только для вызова из событий ПередЗаписью
, ОбработкаПроведения
и т.п., не открывайте внутри метода транзакций. Они гарантированно будут вложенными.
В стандарте не указано
Имеет смысл добавить проверку, гарантирующую, что метод выполняется внутри транзакции, или описать это формально в описании к методу.
1.4.3.¶
Для детализации сообщений об ошибках предусмотрите свою обработку исключений. Так текст исключений можно сделать понятнее.
В стандарте не указано
С развитием механизма отображения ошибок скорее всего подход будет изменен.
1.4.4.¶
Не обращайтесь к базе данных, пока активна транзакция, в которой возникло исключение. Это приведет к ошибке вида В этой транзакции уже происходили ошибки
. Помните о неявных обращениях, например для получения представления ссылки.
2.¶
Не делайте транзакции длинными.
2.1.¶
Выполняйте вместе только неделимые операции с точки зрения бизнес-логики.
Например
При проведении документа записывается документ и его движения в регистрах. Если не прошла запись хотя бы в один регистр вся операция проведения должна быть отменена.
2.1.1.¶
Не объединяйте в одну транзакцию такие действия, которые с точки зрения бизнес-логики могут быть выполнены раздельно.
2.1.2.¶
Исключения:
- Обработать несколько несвязных объектов вместе можно для оптимизации скорости работы. Надо правильно подобрать порции. Стремиться достигнуть золотую середину между длительностью одной транзакции и объемом фиксируемых данных с одной стороны и количеством транзакций с другой стороны.
2.2.¶
Избегайте транзакций, которые выполняются долго.
Неправильно
При загрузке адресного классификатора записывать все данные, относящиеся к одной версии классификатора в одной транзакции. В случае ошибки откатить целиком загружаемую версию классификатора.
Данных по одной версии классификатора много (объем около 1 Гб). Для выполнения такой транзакции,
- может не хватить оперативной памяти (особенно при использовании файловой информационной базы на 32-разрядной ОС)
- такая операция будет выполняться достаточно долго и ее не получится оптимизировать за счет выполнения в несколько потоков
Правильно
Разбить загрузку новой версии классификатора на небольшие транзакции и реализовать функциональность по откату к предыдущей версии в случае ошибки.
2.2.1.¶
Чем дольше выполняется транзакция, тем большее время будут заняты ресурсы сервера и СУБД.
Долгий захват ресурсов снизит эффективность работы системы:
- журнал транзакций: в него пишутся все изменения в базе данных, выполненные в транзакции, чтобы их правильно отменить;
- блокировки, установленные внутри транзакции, будут действовать до конца транзакции;
- на сервере блокировки занимают оперативную память;
- другие ресурсы, необходимые бизнес-логике, которая выполняет транзакцию.
2.2.2.¶
Если транзакция пересеклась по блокировкам с другой транзакцией, она будет ждать возможности установить блокировку. Время ожидания по умолчанию - 20 секунд. Когда время ожидания истекло, транзакция будет завершена с исключением Превышено время ожидания установки блокировки
.
Поэтому длинные транзакции снижают удобство параллельной работы нескольких пользователей.
Проведите анализ, обращая внимание на следующее:
- возможно, какие-то действия можно вынести за транзакцию 2.1.1;
- постарайтесь оптимизировать алгоритм выполнения действия;
- проанализируйте оптимальность устанавливаемых блокировок
2.3.¶
Выполняйте вместе только неделимые операции с точки зрения бизнес-логики.
В стандарте не указано
Дополняет 2.1.
- сложные, ресурсоемкие расчеты нужно стремиться делать до начала транзакции, если это позволяет бизнес-логика;
- если расчет должен выполняться в транзакции, то нужно стремиться сделать его как можно более простым;
Например
Контроль остатков можно делать уже после записи простым запросом к записываемому регистру;
- проверка заполнения объекта должна делаться вне транзакции (см. Проверки, выполняемые в и вне транзакции записи объекта #std463);
- запросы, перед выполнением которых не нужно устанавливать блокировку данных, нужно стремиться выполнять до начала транзакции (см. Ответственное чтение данных #std648);
- запросы, выполняемые в рамках транзакций нужно стремиться оптимизировать (см. группу стандартов Оптимизация запросов)