Странное поведение TRY CATCH, возникающее при вставке записей в WHILE LOOP - PullRequest
1 голос
/ 12 февраля 2020

Ниже приведен код, который я запускаю (готов для копирования и вставки на SQL сервер). По сути, я пытаюсь использовать WHILE L OOP для вставки записей из одной таблицы в новую таблицу один за другим и записывать результаты каждой вставки, используя третью таблицу. Как только я смогу решить эту проблему, этот код будет настроен и использован в нескольких хранимых процедурах.

Вот код:

SET NOCOUNT ON;

BEGIN TRY
    BEGIN TRANSACTION

    --Create dummy tables

    DROP TABLE IF EXISTS #OldTable
    DROP TABLE IF EXISTS #NewTable
    DROP TABLE IF EXISTS #LoggingTable

    CREATE TABLE #OldTable (
        ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
        ,OldValue varchar(64)
    )

    INSERT INTO #OldTable
    VALUES
        ('1')
        ,('2')
        ,('3')
        ,('Four')
        ,('Five')
        ,('6')
        ,('Seven')

    CREATE TABLE #NewTable (
        ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
        ,NewValue int
    )

    CREATE TABLE #LoggingTable (
        ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
        ,OldTableID int NULL
        ,NewTableID int NULL
        ,InsertStatus varchar(MAX)
    )

    --Begin insert loop

    DECLARE @currentID int = NULL

    DECLARE THIS_CURSOR CURSOR FAST_FORWARD FOR
        SELECT ID FROM #OldTable ORDER BY ID

    OPEN THIS_CURSOR
    FETCH NEXT FROM THIS_CURSOR INTO @currentID

    WHILE @@FETCH_STATUS = 0
    BEGIN
        BEGIN TRY
            --Perform insert
            INSERT INTO #NewTable
            OUTPUT @currentID, INSERTED.ID, 'Insert successful' INTO #LoggingTable (OldTableID, NewTableID, InsertStatus)
            SELECT CAST(OldValue AS int) FROM #OldTable WHERE ID = @currentID

            FETCH NEXT FROM THIS_CURSOR INTO @currentID
        END TRY
        BEGIN CATCH
            INSERT INTO #LoggingTable (OldTableID, NewTableID, InsertStatus) VALUES (@currentID, NULL, 'Error occurred during insert operation')
            FETCH NEXT FROM THIS_CURSOR INTO @currentID
        END CATCH   
    END

    CLOSE THIS_CURSOR
    DEALLOCATE THIS_CURSOR

    SELECT * FROM #OldTable
    SELECT * FROM #NewTable
    SELECT * FROM #LoggingTable

    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    PRINT 'An error has occurred';

    -- Test if the transaction is uncommittable.  
    IF XACT_STATE() = -1  
    BEGIN  
        PRINT 'The transaction is in an uncommittable state. Rolling back transaction.'; 
        ROLLBACK TRANSACTION;  
    END;

    -- Test if the transaction is committable.  
    IF XACT_STATE() = 1  
    BEGIN  
        PRINT 'The transaction is committable. Committing transaction.';
        COMMIT TRANSACTION;     
    END;

    THROW;
END CATCH

Вот сообщение об ошибке, которое я получаю:

Текущая транзакция не может быть зафиксирована и не может поддерживать операции записи в файл журнала. Откатите транзакцию.

Эта строка выглядит так:

INSERT INTO #LoggingTable (OldTableID, NewTableID, InsertStatus) VALUES (@currentID, NULL, 'Error occurred during insert operation')

Однако, если я закомментирую эту строку (которая мне все еще нужна!), Она просто переместится на другая строка в коде, что обычно означает, что происходит что-то еще. Я уже провел тщательный поиск в Интернете и до сих пор не уверен, в чем причина этой ошибки.

Самая странная часть этой ошибки - если вы удалите внешние TRY CATCH и TRANSACTION, скрипт работает точно так, как задумано, без проблем; тем не менее, это не жизнеспособное решение IMHO из-за необходимости надлежащего перехвата ошибок и обработки транзакций, как только оно включено в хранимую процедуру.

Ответы [ 2 ]

1 голос
/ 12 февраля 2020

Я переместил пару строк "COMMIT TRANSACTION", и она работает нормально (исключение выдается, потому что он пытается записать в таблицу в блоке catch перед откатом транзакции):

SET NOCOUNT ON;

BEGIN TRY
    SET XACT_ABORT ON;
    BEGIN TRANSACTION;

    --Create dummy tables
    DROP TABLE IF EXISTS #OldTable;
    DROP TABLE IF EXISTS #NewTable;
    DROP TABLE IF EXISTS #LoggingTable;

    CREATE TABLE #OldTable
    (
        ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
        OldValue VARCHAR(64)
    );

    INSERT INTO #OldTable
    VALUES
    ('1'),
    ('2'),
    ('3'),
    ('Four'),
    ('Five'),
    ('6'),
    ('Seven');

    CREATE TABLE #NewTable
    (
        ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
        NewValue INT
    );

    CREATE TABLE #LoggingTable
    (
        ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
        OldTableID INT NULL,
        NewTableID INT NULL,
        InsertStatus VARCHAR(MAX)
    );

    --Begin insert loop

    DECLARE @currentID INT = NULL;

    DECLARE THIS_CURSOR CURSOR FAST_FORWARD FOR
    SELECT ID
    FROM #OldTable
    ORDER BY ID;

    OPEN THIS_CURSOR;
    FETCH NEXT FROM THIS_CURSOR
    INTO @currentID;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        BEGIN TRY
            --Perform insert
            INSERT INTO #NewTable
            OUTPUT @currentID,
                   INSERTED.ID,
                   'Insert successful'
            INTO #LoggingTable
            (
                OldTableID,
                NewTableID,
                InsertStatus
            )
            SELECT CAST(OldValue AS INT)
            FROM #OldTable
            WHERE ID = @currentID;

            FETCH NEXT FROM THIS_CURSOR
            INTO @currentID;
            COMMIT TRANSACTION;
        END TRY
        BEGIN CATCH
            INSERT INTO #LoggingTable
            (
                OldTableID,
                NewTableID,
                InsertStatus
            )
            VALUES
            (@currentID, NULL, 'Error occurred during insert operation');
            FETCH NEXT FROM THIS_CURSOR
            INTO @currentID;
        END CATCH;
    END;

    CLOSE THIS_CURSOR;
    DEALLOCATE THIS_CURSOR;

    SELECT *
    FROM #OldTable;
    SELECT *
    FROM #NewTable;
    SELECT *
    FROM #LoggingTable;


    SET XACT_ABORT OFF;
END TRY
BEGIN CATCH
    PRINT 'An error has occurred';

    -- Test if the transaction is uncommittable.  
    IF XACT_STATE() = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state. Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

    -- Test if the transaction is committable.  
    IF XACT_STATE() = 1
    BEGIN
        PRINT 'The transaction is committable. Committing transaction.';
        COMMIT TRANSACTION;
    END;

    THROW;
END CATCH;
0 голосов
/ 13 февраля 2020

Если честно, я думаю, что сделка здесь просто усложняет ситуацию, чем она должна быть. Существует неявная транзакция вокруг каждого оператора DML. Тело вашего курсора - это не что иное, как оператор вставки с блоком catch, если вставка не удалась. Я удалил кучу постороннего кода, имеющего дело с явными транзакциями, и ваш пример работает отлично.

--Create dummy tables

DROP TABLE IF EXISTS #OldTable
DROP TABLE IF EXISTS #NewTable
DROP TABLE IF EXISTS #LoggingTable

CREATE TABLE #OldTable (
    ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
    ,OldValue varchar(64)
)

INSERT INTO #OldTable
VALUES
    ('1')
    ,('2')
    ,('3')
    ,('Four')
    ,('Five')
    ,('6')
    ,('Seven')

CREATE TABLE #NewTable (
    ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
    ,NewValue int
)

CREATE TABLE #LoggingTable (
    ID int IDENTITY(1,1) NOT NULL PRIMARY KEY
    ,OldTableID int NULL
    ,NewTableID int NULL
    ,InsertStatus varchar(MAX)
)

--Begin insert loop

DECLARE @currentID int = NULL

DECLARE THIS_CURSOR CURSOR FAST_FORWARD FOR
    SELECT ID FROM #OldTable ORDER BY ID

OPEN THIS_CURSOR
FETCH NEXT FROM THIS_CURSOR INTO @currentID

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        --Perform insert
        INSERT INTO #NewTable
        OUTPUT @currentID, INSERTED.ID, 'Insert successful' INTO #LoggingTable (OldTableID, NewTableID, InsertStatus)
        SELECT CAST(OldValue AS int) FROM #OldTable WHERE ID = @currentID

        FETCH NEXT FROM THIS_CURSOR INTO @currentID
    END TRY
    BEGIN CATCH
        INSERT INTO #LoggingTable (OldTableID, NewTableID, InsertStatus) VALUES (@currentID, NULL, 'Error occurred during insert operation')
        FETCH NEXT FROM THIS_CURSOR INTO @currentID
    END CATCH   
END

close THIS_CURSOR
deallocate THIS_CURSOR

SELECT * FROM #OldTable
SELECT * FROM #NewTable
SELECT * FROM #LoggingTable
...