Ошибки базы данных и транзакции

При работе с базой данных могут происходить ошибки. В 1С:Предприятии 8 ошибки базы данных подразделяются на следующие две категории:

К невосстановимым относятся такие ошибки базы данных, при возникновении которых функционирование 1С:Предприятия 8 может быть серьезно нарушено. Поэтому, во избежание более серьезных неприятностей (например, порчи данных),  при возникновении невосстановимой ошибки выполнение 1С:Предприятия 8 прекращается. Если невосстановимая ошибка базы данных произошла в процессе выполнения транзакции, то все изменения сделанные в рамках этой транзакции отменяются.

При возникновении восстановимой ошибки считается, что серьезных нарушений в работе 1С:Предприятия 8 не произошло и работа может быть продолжена, но сама вызвавшая ошибку операция прекращается, и вызывается исключение, которое может быть перехвачено и обработано средствами встроенного языка. Казалось бы, все понятно. Но есть тонкость. Если восстановимая ошибка базы данных произошла в процессе выполнения транзакции, то, вне зависимости от того, было исключение, вызванное этой ошибкой, перехвачено и обработано или нет, транзакция уже не может быть продолжена или зафиксирована. Единственная операция с базой данных, которую можно произвести в такой ситуации - это отмена транзакции. Таким образом, приведенный фрагмент кода не вполне корректен:

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

Если при выполнении оператора Данные.Записать() произойдет восстановимая ошибка базы данных (например, по причине того, что элемент данных был заблокирован другим пользователем), то вызванное этой ошибкой исключение будет перехвачено, но повторное выполнение этого же оператора в цикле уже безусловно приведет к ошибке, так как при выполнении данной транзакции уже имела место ошибка базы данных. Следует заметить, что не всякая ошибка приводит к невозможности продолжения выполнения и фиксации транзакции, а именно ошибка базы данных. Исключения, не имеющие отношения к ошибкам базы данных, никакого влияния на возможность продолжения выполнения транзакции не оказывают.

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

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

В данном варианте, если при выполнении оператора Данные.Записать() произойдет восстановимая ошибка базы данных, то при обработке исключения, вызванного этой ошибкой, транзакция будет отменена и при повторном выполнении цикла транзакция будет начата сначала.

Следует однако сделать предостережение. Дело в том, что в рамках уже выполняемой транзакции можно обращаться к методам НачатьТранзакцию(), ЗафиксироватьТранзакцию() и ОтменитьТранзакцию(). Однако вызов метода НачатьТранзакцию() при уже выполняющейся транзакции не означает начала новой транзакции. В этом случае просто произойдет увеличение на 1 значения внутреннего счетчика транзакций. Метод НачатьТранзакцию() начинает новую транзакцию только в том случае, если значение внутреннего счетчика транзакций равно 0. Аналогично, обращение к методам ЗафиксироватьТранзакцию() или ОтменитьТранзакцию() приводит к реальному завершению транзакции только в том случае, если значение внутреннего счетчика транзакций равно 1. Если при значении счетчика транзакций большем 1 произойдет обращение к методу ЗафиксироватьТранзакцию(), то значение счетчика будет просто уменьшено на 1:

Копировать в буфер обмена
НачатьТранзакцию();        // Начало транзакции - счетчик становится равным 1
...
НачатьТранзакцию();        // Счетчик увеличивается на 1 и становится равным 2
...
ЗафиксироватьТранзакцию(); // Счетчик уменьшается на 1 и становится равным 1
...
ЗафиксироватьТранзакцию(); // Фиксация транзакции

Если же при значении счетчика траназкций большем 1 произойдет обращение к методу ОтменитьТранзакцию(), то значение счетчика транзакций не только будет уменьшено на 1, но и произойдет установка признака, не позволяющего зафиксировать результаты выполнения всей транзакции в целом. И последующее обращение к методу ЗафиксироватьТранзакцию(), выполняемое при значении счетчика транзакций равном 1, фактически приведет к отмене  транзакции:

Копировать в буфер обмена
НачатьТранзакцию();        // Начало транзакции - счетчик становится равным 1
...
НачатьТранзакцию();        // Счетчик увеличивается на 1 и становится равным 2
...
ОтменитьТранзакцию();      // Счетчик уменьшается на 1 и становится равным 1, устанавливается признак отмены транзакции.
...
ЗафиксироватьТранзакцию(); // Фактическая отмена транзакции

Аналогично, ошибка базы данных, произошедшая при значении счетчика транзакций большем 1, приведет к невозможности продолжения и фиксации всей транзакции в целом.

Таким образом, не всегда можно быть уверенным, что обращение к методу НачатьТранзакцию() действительно начинает новую транзакцию, а обращение к методам ЗафиксироватьТранзакцию() и ОтменитьТранзакцию() означает завершение транзакции в целом. Поэтому, конструкции с циклическим повторением попытки "прорваться через транзакцию" следует применять только в тех случаях, когда есть уверенность, что обращение к методу НачатьТранзакцию() действительно начинает новую транзакцию. И только при условии, что действительно есть необходимость "прорваться". Во многих случаях, при возникновении ошибки, разумнее выдать сообщение пользователю и предоставить ему самому решать: повторить попытку выполнения операции еще раз, или перед повторением попытки следует предпринять какие-то действия по разрешению ситуации, приведшей к ошибке.