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

How to log errors even if the transaction is rolled back?

假设我们有以下命令:

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 服务器中实现它?

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

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

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

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

然后您可以在 table 变量中跟踪日志:

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;

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

此外,在上面的示例中,保存点没有任何实际用途,因为即使 'successful' 如果在批处理中检测到任何错误,最终也会回滚客户插入。

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