Я столкнулся с процедурой в SQL Server 2017, в которой транзакция выполняется в блоке try-catch
.Он не является вложенным, просто заполняется и циклически обрабатывается таблица идентификаторов с помощью курсора.Так что try-catch
находится внутри цикла, вызывается какая-то другая процедура.Иногда эта процедура завершается с ошибкой нарушения ограничения, и вполне нормально сохранить все, что было успешно, до внутреннего исключения.А потом я наткнулся на коммит в предложении catch.Это заставило меня задуматься, и я написал этот код:
DECLARE @Table TABLE (ID INT NOT NULL PRIMARY KEY)
DECLARE @Input TABLE (ID INT)
INSERT INTO @Input
VALUES (1), (1), (2), (NULL), (3)
DECLARE @Output TABLE (ID INT)
--SET XACT_ABORT OFF
DECLARE @ID int
DECLARE [Sequence] CURSOR LOCAL FAST_FORWARD FOR
SELECT ID FROM @Input
OPEN [Sequence]
FETCH NEXT FROM [Sequence] INTO @ID
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
DECLARE @Msg nvarchar(max) = 'Inserting ''' + TRY_CAST(@ID as varchar(11)) + ''''
RAISERROR (@Msg, 0, 0) WITH NOWAIT
-- Order is important
--INSERT INTO @Table VALUES (@ID)
INSERT INTO @Output VALUES (@ID)
INSERT INTO @Table VALUES (@ID)
COMMIT TRAN
END TRY
BEGIN CATCH
SET @Msg = 'Caught ' + CAST(ERROR_NUMBER() as varchar(11)) + ' : ' + ERROR_MESSAGE()
RAISERROR (@Msg, 1, 1) WITH NOWAIT
IF XACT_STATE() = -1
BEGIN
SET @Msg = 'Uncommitable transaction [-1]'
RAISERROR (@Msg, 1, 1) WITH NOWAIT
ROLLBACK TRAN
END
IF XACT_STATE() = 1
BEGIN
SET @Msg = 'Commitable transaction [1]'
RAISERROR (@Msg, 1, 1) WITH NOWAIT
COMMIT TRAN
END
END CATCH
FETCH NEXT FROM [Sequence] INTO @ID
END
SELECT * FROM @Table
SELECT * FROM @Output
Так что, когда я попытался поменять порядок вставок @Output
и @Table
, я получил различные результаты, независимо от того, чтоXACT_ABORT
установлен на или я фиксирую или откат транзакции в блоке catch.Я всегда был уверен, что все откатится, и таблицы @Output
и @Table
будут равны ....
Что я здесь не так делаю?Это поведение транзакции по умолчанию?