Вопрос
В документации из 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
- Создать таблицу Dummy с индексом UNIQUE
- Хранимая процедура, которая вставляется в Dummy. Процедуры разрешают код
XACT_ABORT
. - , который выполняет эту процедуру дважды в транзакции. Второй вызов завершается неудачно, так как он пытается вставить дублирующее значение в Dummy.
- Этот же код выводит значение
@@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
.