Транзакция не откатывает все изменения - PullRequest
0 голосов
/ 03 января 2019

Я столкнулся с процедурой в 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 будут равны ....

Что я здесь не так делаю?Это поведение транзакции по умолчанию?

Ответы [ 2 ]

0 голосов
/ 03 января 2019

Как напомнил Бен Тул , здесь должны использоваться только временные или обычные таблицы.Поэтому, когда исключение обнаружено и XACT_STATE() = 1 (Подтверждаемая транзакция), COMMIT сохранит все, что было успешно выполнено, и ROLLBACK отменит все это.

    IF XACT_STATE() = 1
    BEGIN
        SET @Msg = 'Commitable transaction [1]'
        RAISERROR (@Msg, 1, 1) WITH NOWAIT
        COMMIT TRAN  -- Keep changes or undo everything (ROLLBACK)
    END

Результаты таблицы результатов:
ROLLBACK: [1,2,3]
COMMIT: [1,1,2, NULL, 3]

0 голосов
/ 03 января 2019

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

Обратите внимание, что ваша последовательность все равно будет иметь значения, извлеченные из нее. Даже если вы добавите это в сделку.

...