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

#std783

Область применения: управляемое приложение, мобильное приложение, обычное приложение.

Транзакции применяются для целостного изменения связанных данных, т.е. все действия с базой данных, выполняемые в рамках транзакции или выполняются целиком, или целиком откатываются.

1. Использование транзакций в 1С:Предприятии обладает рядом особенностей:

Эти особенности накладывают ряд требований к написанию кода с использованием транзакций. Несоблюдение этих требований может приводить к возникновению ошибок вида «В этой транзакции уже происходили ошибки», которые может быть крайне сложно воспроизвести и отладить.

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

1.2. Начало транзакции и ее фиксация (отмена) должны происходить в контексте одного метода

Правильно

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

    НачатьТранзакцию();

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

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

Неправильно

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

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

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

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

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

1.3. При использовании транзакций необходимо предусмотреть обработку исключений, придерживаясь следующих правил:

Пример

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

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

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

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

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

    ВызватьИсключение; // есть внешняя транзакция

КонецПопытки;

1.4. Использование вложенных транзакций приводит к усложнению кода. Принимая решение об использовании этой возможности, нужно очень взвешенно оценить решаемую задачу: возможно, это усложнение просто не оправдано.

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

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

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

Правильно

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

Неправильно

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

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

1.4.3. При необходимости повысить качество сообщений об ошибках – на каждом уровне разработчик может предусмотреть свою обработку исключений, для чего, возможно, потребуется открыть вложенную транзакцию.

Пример. Вызывается метод ДобавитьЭлектроннуюПодпись. Внутри, если что-то пошло не так, нужно обработать исключение и добавить текст вида: «Не удалось добавить электронную подпись к объекту %ПредставлениеОбъекта% по причине:%ОписаниеОшибки%». В противном случае исключение будет обработано выше по стеку вызовов, например, при записи файла и будет выдано сообщение вида: «Не удалось записать файл %ИмяФайла% по причине: %ОписаниеОшибки%», где в «%ОписаниеОшибки%», будет просто указание на строчку кода и пользователю будет непонятно, зачем вообще программа записывала файл, если он просто его подписывал.

1.4.4. При обработке исключения, если транзакция все еще активна, например, исключение возникло во вложенной транзакции, нельзя обращаться к базе данных, так как это приведет к исключению «В этой транзакции уже происходили ошибки». При этом нужно учитывать, что обращение к базе данных может быть неявным, например, для получения представления ссылки.

2. Ограничение на длину (продолжительность) транзакции.

2.1. В общем случае в рамках одной транзакции нужно выполнять только те действия, которые неделимы, исходя из бизнес-логики.

Пример. При проведении документа записывается документ и его движения в регистрах. Если не прошла запись хотя бы в один регистр вся операция проведения должна быть отменена.

2.1.1. Если с точки зрения бизнес-логики действия могут быть выполнены по отдельности, то их в общем случае не следует объединять в одну транзакцию.

2.1.2. Исключением из п.2.1.1 могут быть случаи, когда с целью оптимизации несколько несвязанных объектов обрабатываются в рамках одной транзакции. В этом случае необходимо взвешенно подходить к выбору порции обработки данных: нужно стремиться к достижению золотой середины между длительностью одной транзакции и объемом фиксируемых данных с одной стороны и количеством транзакций с другой.

2.2. Следует избегать транзакций, которые выполняются длительное время.

Например, неправильно: для загрузки адресного классификатора записывать все данные, относящиеся к одной версии классификатора в одной транзакции, для того, чтобы в случае ошибки откатить целиком загружаемую версию классификатора. Т.к. данных по одной версии классификатора много (объем около 1 Гб), то для выполнения такой транзакции, во-первых, может не хватить оперативной памяти (особенно при использовании файловой информационной базы на 32-разрядной ОС), а, во-вторых, такая операция будет выполняться достаточно долго и ее нельзя будет оптимизировать за счет выполнения в несколько потоков.

Правильно: разбить загрузку новой версии классификатора на небольшие порции так, чтобы запись порции в одной транзакции не превышала 20 секунд в условиях высоконагруженной информационной системы и реализовать функциональность по откату к предыдущей версии в случае ошибки. Максимальная продолжительность указана исходя из того, что время ожидания установки транзакционной блокировки данных в информационной базе по умолчанию равно 20 сек.

См. также Особенности использования транзакций при обмене данными.

2.2.1 Чем дольше выполняется транзакция, тем большее время будут заняты ресурсы сервера 1С:Предприятия и СУБД. Как правило длинные транзакции занимают следующие ресурсы:

Все это в целом может снижать эффективность использования ресурсов.

2.2.2. Если две транзакции пересекаются по блокируемым ресурсам, то транзакция, которая начала выполняться позже, будет ожидать возможность установления блокировки ограниченное время (по умолчанию – 20 секунд), после чего будет завершена с исключением «Превышено время ожидания установки блокировки». Поэтому длинные транзакции могут сильно снижать удобство параллельной работы пользователей.

Возникновение таких исключений – это повод провести анализ действий, которые выполняются в конфликтующих транзакциях

2.3. В рамках транзакции нужно стремиться выполнять минимум действий – только те, которые нельзя в соответствии с бизнес-логикой выполнять вне транзакции. В частности:

3. Обязательное использование транзакции.

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

Пример:

Неправильно

    РегистрыСведений.КурсыВалют.УстановитьИспользованиеИтогов(Ложь);
   
    НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Валюта.Установить(ВалютаСсылка);
    НаборЗаписей.Загрузить(ТаблицаКурсов);
    НаборЗаписей.ОбменДанными.Загрузка = Истина;
   
    НаборЗаписей.Записать();
   
    РегистрыСведений.КурсыВалют.УстановитьИспользованиеИтогов(Истина);
    РегистрыСведений.КурсыВалют.ПересчитатьИтоги();

Правильно

    НачатьТранзакцию();
    Попытка
        РегистрыСведений.КурсыВалют.УстановитьИспользованиеИтогов(Ложь);
       
        НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Валюта.Установить(ВалютаСсылка);
        НаборЗаписей.Загрузить(ТаблицаКурсов);
        НаборЗаписей.ОбменДанными.Загрузка = Истина;
       
        НаборЗаписей.Записать();
       
        РегистрыСведений.КурсыВалют.УстановитьИспользованиеИтогов(Истина);
       
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение;
    КонецПопытки;
   
    РегистрыСведений.КурсыВалют.ПересчитатьИтоги();