即使事务被回滚,如何记录错误?

时间:2015-04-18 09:23:27

标签: sql sql-server transactions

假设我们有以下命令:

SET XACT_ABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF

DECLARE @index int 
SET @index = 4;

DECLARE @errorCount int
SET @errorCount = 0;

BEGIN TRANSACTION
    WHILE @index > 0
    BEGIN
        SAVE TRANSACTION Foo;
        BEGIN TRY
            -- commands to execute...
            INSERT INTO AppDb.dbo.Customers VALUES('Jalal', '1990-03-02');

            -- make a problem
            IF @index = 3
                INSERT INTO AppDb.dbo.Customers VALUES('Jalal', '9999-99-99'); 
        END TRY
        BEGIN CATCH
            ROLLBACK TRANSACTION Foo; -- I want to keep track of previous logs but not works! :(
            INSERT INTO AppDb.dbo.LogScripts VALUES(NULL, 'error', 'Customers', suser_name());
            SET @errorCount = @errorCount + 1;
        END CATCH
        SET @index = @index - 1;
    END
IF @errorCount > 0
    ROLLBACK TRANSACTION
ELSE
    COMMIT TRANSACTION

我想执行批处理,将所有错误保留在日志中,然后,如果没有发生错误,则提交所有更改。如何在Sql Server中实现它?

1 个答案:

答案 0 :(得分:3)

事务与连接相关联,因此,所有写入都将在外部ROLLBACK TRANSACTION上回滚(与嵌套保存点无关)。

您可以做的是将错误记录到内存结构中,如Table Variable,然后,在提交/回滚外部事务之后,您可以插入收集的日志。

为简洁起见,我简化了您的LogsCustomers表:

CREATE TABLE [dbo].[Logs](
    [Description] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

CREATE TABLE [dbo].[Customers](
    [ID] [int] NOT NULL,
    [Name] [nvarchar](50) NULL
);
GO

然后您可以跟踪表变量中的日志:

SET XACT_ABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF
GO

DECLARE @index int;
SET @index = 4;

DECLARE @errorCount int
SET @errorCount = 0;

-- In memory storage to accumulate logs, outside of the transaction
DECLARE @TempLogs AS TABLE (Description NVARCHAR(MAX));

BEGIN TRANSACTION
    WHILE @index > 0
    BEGIN
        -- SAVE TRANSACTION Foo; As per commentary below, savepoint is futile here
        BEGIN TRY
            -- commands to execute...
            INSERT INTO Customers VALUES(1, 'Jalal');

            -- make a problem
            IF @index = 3
                INSERT INTO Customers VALUES(NULL, 'Broken'); 
        END TRY
        BEGIN CATCH
            -- ROLLBACK TRANSACTION Foo; -- Would roll back to the savepoint
            INSERT INTO @TempLogs(Description) 
               VALUES ('Something bad happened on index ' + CAST(@index AS VARCHAR(50)));
            SET @errorCount = @errorCount + 1;
        END CATCH
        SET @index = @index - 1;
    END
IF @errorCount > 0
    ROLLBACK TRANSACTION
ELSE
    COMMIT TRANSACTION
-- Finally, do the actual insertion of logs, outside the boundaries of the transaction.
INSERT INTO dbo.Logs(Description)
    SELECT Description FROM @TempLogs;

需要注意的一点是,这是处理数据的一种非常昂贵的方式(即尝试插入所有数据,然后在遇到任何问题时回滚批处理)。这里的另一种选择是在尝试插入任何数据之前验证所有数据(以及返回和报告错误)。

此外,在上面的示例中,Savepoint没有任何实际用途,因为如果检测到批次的任何错误,即使“成功”的客户插入也将最终回滚。

SqlFiddle here - 循环完成,尽管插入了3个客户,但ROLLBACK TRANSACTION删除了所有成功插入的客户。但是,仍然会写入日志,因为表变量不受外部事务的影响。