XACT_ABORT не всегда откатывает транзакцию при ошибке. Когда он делает это точно? - PullRequest
2 голосов
/ 21 февраля 2020

Вопрос

В документации из SET XACT_ABORT немного сказано об эффекте включения этой опции.

Когда SET XACT_ABORT включен, если оператор Transact- SQL вызывает ошибку времени выполнения, вся транзакция завершается и откатывается.

Я не верю, что это полная правда. Прочитав это, я забеспокоился, что если хранимая процедура, которая включает эту опцию, будет выполнена в транзакции, созданной внешним процессом, это может привести к откату внешней транзакции. К счастью, мои опасения оказались необоснованными. Однако теперь это означает, что я не совсем понимаю, как работает XACT_ABORT. Каковы условия SQL Сервер проверяет, следует ли откатить транзакцию или нет?

Предварительное расследование

Я провел следующий эксперимент: (краткое описание этого кода приведено ниже. , поскольку имеет нумерованный список до того, как блок кода нарушает форматирование StackOverflow, duh)

CREATE TABLE Dummy
(
    ID INT NOT NULL IDENTITY CONSTRAINT PK_Dummy PRIMARY KEY,
    Text NVARCHAR(128) NOT NULL
)

CREATE UNIQUE NONCLUSTERED INDEX IX_Dummy_Text ON dbo.Dummy(Text)

GO 

CREATE OR ALTER PROCEDURE InsertDummy
    @Text NVARCHAR(128)
AS
BEGIN
    SET NOCOUNT OFF
    SET XACT_ABORT ON

    INSERT dbo.Dummy (Text) VALUES (@Text)
END

GO

SET XACT_ABORT ON

BEGIN TRANSACTION
BEGIN TRY
    EXEC dbo.InsertDummy @Text = N'Dummy'
    EXEC dbo.InsertDummy @Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
    PRINT 'ERROR! @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)

    -- Echo the error
    DECLARE @ErrorMessage NVARCHAR(4000);  
    DECLARE @ErrorSeverity INT;  
    DECLARE @ErrorState INT;  

    SELECT @ErrorMessage = ERROR_MESSAGE();  
    SELECT @ErrorSeverity = ERROR_SEVERITY();  
    SELECT @ErrorState = ERROR_STATE();  

    RAISERROR (@ErrorMessage, -- Message text.  
                @ErrorSeverity, -- Severity.  
                @ErrorState -- State.  
                );  
END CATCH

PRINT 'At the end @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)
IF @@TRANCOUNT>0
    ROLLBACK
  1. Создать таблицу Dummy с индексом UNIQUE
  2. Хранимая процедура, которая вставляется в Dummy. Процедуры разрешают код XACT_ABORT.
  3. , который выполняет эту процедуру дважды в транзакции. Второй вызов завершается неудачно, так как он пытается вставить дублирующее значение в Dummy.
  4. Этот же код выводит значение @@TRANCOUNT, чтобы показать, находимся ли мы в транзакции или нет. Он также включает XACT_ABORT.

Результат этого теста:

(1 row affected)

(0 rows affected)
ERROR! @@TRANCOUNT is 1
Msg 50000, Level 14, State 1, Line 74
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
At the end @@TRANCOUNT is 1

Произошла ошибка, но транзакция не откатилась. Способ работы этого параметра явно не так прост c, как мне бы поверила в документации. Почему транзакция не была отменена?

В этом ответе упоминается, что XACT_ABORT откатывает транзакцию только в том случае, если серьезность ошибки составляет не менее 16. Ошибка в этом примере только уровень 14. Однако, даже если я заменю INSERT в процедуре на RAISERROR (N'Custom error', 16, 0), транзакция все равно не будет откатана.

ОБНОВЛЕНИЕ: Что я обнаружил, хотя транзакция не откатился в моем тесте, он обречен! @@TRANCOUNT равно 1, когда я выполняю этот пример независимо от настройки XACT_ABORT: но если значение ON, XACT_STATE() равно -1, что указывает на некомпетентную транзакцию. Когда XACT_ABORT равно OFF, XACT_STATE() равно 1.

1 Ответ

0 голосов
/ 21 февраля 2020

Вопрос "Произошла ошибка, но транзакция не откатана. Способ работы этих настроек явно не так прост c, как мне хотелось бы, чтобы документация заставила меня поверить. Почему была транзакция? не откат "

Ответ на этот вопрос заключается в том, что RAISERROR не будет вызывать XACT_ABORT для запуска! Это означает, что мы можем быть в очень испорченном состоянии транзакции Прервать, Прервать, Мы XACT_ABORT: ing, Или Мы?!

Согласно MSDN,

Оператор THROW соблюдает SET XACT_ABORT. RAISERROR нет. Новые приложения должны использовать THROW вместо RAISERROR.

Мы можем использовать инструкцию THROW вместо RAISERROR. Таким образом, мы можем использовать следующий оператор для запуска XACT_ABORT

TRUNCATE TABLE Dummy
GO
SET XACT_ABORT ON

BEGIN TRANSACTION
BEGIN TRY
    EXEC dbo.InsertDummy @Text = N'Dummy'
    EXEC dbo.InsertDummy @Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
THROW
END CATCH

PRINT 'At the end @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)
IF @@TRANCOUNT>0
    ROLLBACK

Вывод будет;

(1 row affected)

(0 rows affected)
Msg 2601, Level 14, State 1, Procedure dbo.InsertDummy, Line 7 [Batch Start Line 5]
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).

Для проблемы обновленный вы можете увидеть включить xact_abort и попробовать поймать вместе

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...