即使事务被回滚,如何记录错误?
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,然后,在提交/回滚外部事务后,您可以插入收集的日志。
为了简洁起见,我简化了您的 Logs
和 Customers
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 变量不受外部事务的影响。
假设我们有以下命令:
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,然后,在提交/回滚外部事务后,您可以插入收集的日志。
为了简洁起见,我简化了您的 Logs
和 Customers
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 变量不受外部事务的影响。