Перейти к содержанию
#std783

Транзакции: правила использования

Транзакция - это действие, которое может быть выполнено только целиком. Если часть действия не выполнена - все действие отменяется.

1.

Ошибка В этой транзакции уже происходили ошибки возникает из-за несоблюдения требований к написанию кода. Такие ошибки сложно воспроизводить и отлаживать их исправление.

Особенности работы с транзакциями в платформе:

1.1.

Исключение не отменяет транзакцию. Оно взводит служебный флажок транзакции Запрет успешного завершения. Реальная отмена произойдет тогда, когда код дойдет до функции ЗафиксироватьТранзакцию или ОтменитьТранзакцию. Для каждого метода НачатьТранзакцию должны быть парные методы ЗафиксироватьТранзакцию и ОтменитьТранзакцию.

В стандарте не указано

О том что такое флажки транзакции и как работает платформа смотрите в:

1.2.

Начало транзакции и ее завершение (фиксацию) выполняйте в одном методе. Не должно быть разнесения начала и конца на несколько функций. Тот, кто будет расследовать ошибку, скажет спасибо!

Неправильно

Процедура ЗаписатьДанныеВИБ()

    НачатьТранзакцию();
    ЗаписатьДокумент();

КонецПроцедуры;

Процедура ЗаписатьДокумент()

    Попытка
        ... // чтение или запись данных
        ДокументОбъект.Записать()
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
    ... // дополнительные действия по обработке исключения
    КонецПопытки;

КонецПроцедуры

Правильно

Процедура ЗаписатьДанныеВИБ()

    НачатьТранзакцию();
    Попытка
        ... // чтение или запись данных
        ДокументОбъект.Записать()
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ... // дополнительные действия по обработке исключения
    КонецПопытки;

КонецПроцедуры
1.3.

Используйте следующий паттерн для работы с транзакциями:

Паттерн

НачатьТранзакцию(); // (1)
Попытка
    БлокировкаДанных = Новый БлокировкаДанных;
    ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("Документ.ПриходнаяНакладная");
    ЭлементБлокировкиДанных.УстановитьЗначение("Ссылка", СсылкаДляОбработки);
    ЭлементБлокировкиДанных.Режим = РежимБлокировкиДанных.Исключительный;
    БлокировкаДанных.Заблокировать();

    ... // чтение или запись данных (2)

    ДокументОбъект.Записать();

    ЗафиксироватьТранзакцию(); // (3)
Исключение
    ОтменитьТранзакцию(); // (4)

    ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"), // (5)
        УровеньЖурналаРегистрации.Ошибка,
        ,
        ,
        ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));

    ВызватьИсключение; // (6)
КонецПопытки;
  1. Метод НачатьТранзакцию должен быть вне блока Попытка-Исключение перед оператором Попытка и ничего между ними;

  2. Все действия, выполняемые после метода НачатьТранзакцию, должны находиться в одном блоке Попытка, в том числе чтение, блокировка и обработка данных;

  3. Метод ЗафиксироватьТранзакцию должен идти последним в блоке Попытка перед оператором Исключение, чтобы гарантировать, что после ЗафиксироватьТранзакцию не возникнет исключение;

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

  5. В блоке Исключение делайте запись в журнал регистрации;

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

1.4.

Если можно избежать использования вложенных транзакций - постарайтесь избежать.

1.4.1.

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

Избегайте открытия транзакции, если не требуется ответственное чтение данных #std648. Например, при записи нового объекта или набора записей регистра обычно ответственное чтение не требуется.

При вызове методов ПолучитьОбъект (или Прочитать для наборов записей) анализируйте должно ли происходить чтение ответственно. Если не должно - не вызывайте НачатьТранзакцию.

Неправильно

НачатьТранзакцию();
Попытка
    ДокументОбъект = Документы.ПриходнаяНакладная.СоздатьДокумент();
    ... // действия по заполнению объекта
    ДокументОбъект.Записать();
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
КонецПопытки;

Правильно

Попытка
    ДокументОбъект = Документы.ПриходнаяНакладная.СоздатьДокумент();
    ... // действия по заполнению объекта
    ДокументОбъект.Записать();
Исключение
    ... // действия по обработке исключения
КонецПопытки;
1.4.2.

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

В стандарте не указано

Имеет смысл добавить проверку, гарантирующую, что метод выполняется внутри транзакции, или описать это формально в описании к методу.

Если Не ТранзакцияАктивна() Тогда
    ВызватьИсключение НСтр("ru = 'Метод предназначен для вызова внутри транзакции.'");
КонецЕсли;
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);
  • запросы, выполняемые в рамках транзакций нужно стремиться оптимизировать (см. группу стандартов Оптимизация запросов)
Источник

https://its.1c.ru/db/v8std#content:783