使用XACT_ABORT,然后尝试在SQL Server Break break tsqlt滚动中一起捕捉 我开始在我的生产代码中使用SQL Server的TSQLT单元测试。 目前,我为SQL Server使用Erland Sommarskog的错误处理模式。 使用tempdb; 设置ansi_nulls,

问题描述 投票:0回答:0

erlandsommarskog建议我们设置XACT_ABORT,因为只有这样,SQL Server才以(主要)一致的方式处理错误。 但是,使用TSQLT时会产生问题。 TSQLT在显式事务内执行所有测试。 测试完成后,整个交易都会回滚。 这使清理测试工件完全无痛。但是,随着XACT_ABORT打开,任何错误都会立即在尝试块内try blocks。该事务。 交易必须完全回滚。 它不能提交,也不能回到保存点。 实际上,直到交易向后回滚,没有什么可以写入该会话内的事务日志。 但是,除非测试结束时交易打开,否则TSQLT无法正确跟踪测试结果。 TSQLT停止执行并为交易丢弃回滚错误。 失败的测试显示出错误状态(而不是成功或失败),并且随后的测试未运行。 TSQLT的创建者Sebastian Meine推荐了不同的eRror处理模式



USE TempDB; SET ANSI_NULLS, QUOTED_IDENTIFIER ON; GO IF OBJECT_ID('dbo.MeineRollback') IS NOT NULL DROP PROCEDURE dbo.MeineRollback; GO CREATE PROCEDURE dbo.MeineRollback AS BEGIN /*Stored Procedure*/ SET NOCOUNT ON; /* We declare the error variables here, populate them inside the CATCH * block and then do our error handling after exiting the CATCH block */ DECLARE @ErrorNumber INT ,@MessageTemplate NVARCHAR(4000) ,@ErrorMessage NVARCHAR(4000) ,@ErrorProcedure NVARCHAR(126) ,@ErrorLine INT ,@ErrorSeverity INT ,@ErrorState INT ,@RaisErrorState INT ,@ErrorLineFeed NCHAR(1) = CHAR(10) ,@ErrorStatus INT = 0 ,@SavepointName VARCHAR(32) = REPLACE( (CAST(NEWID() AS VARCHAR(36))), '-', ''); /*Savepoint names are 32 characters and must be unique. UNIQUEIDs are 36, four of which are dashes.*/ BEGIN TRANSACTION; /*If a transaction is already in progress, this just increments the transaction count*/ SAVE TRANSACTION @SavepointName; BEGIN TRY; RAISERROR('This is a test. Had this been an actual error, Sebastian would have given you a meaningful error message.', 16, 1); END TRY BEGIN CATCH; /* Build a message string with placeholders for the original error information * Note: "%d" & "%s" are placeholders (substitution parameters) which capture * the values from the argument list of the original error message. */ SET @MessageTemplate = N': Error %d, Severity %d, State %d, ' + @ErrorLineFeed + N'Procedure %s, Line %d, ' + @ErrorLineFeed + N', Message: %s'; SELECT @ErrorStatus = 1 ,@ErrorMessage = ERROR_MESSAGE() ,@ErrorNumber = ERROR_NUMBER() ,@ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-') ,@ErrorLine = ERROR_LINE() ,@ErrorSeverity = ERROR_SEVERITY() ,@ErrorState = ERROR_STATE() ,@RaisErrorState = CASE ERROR_STATE() WHEN 0 /*RAISERROR Can't generate errors with State = 0*/ THEN 1 ELSE ERROR_STATE() END; END CATCH; /*Rollback to savepoint if error occurred. This does not affect the transaction count.*/ IF @ErrorStatus <> 0 ROLLBACK TRANSACTION @SavepointName; /*If this procedure executed inside a transaction, then the commit just subtracts one from the transaction count.*/ COMMIT TRANSACTION; IF @ErrorStatus = 0 RETURN 0; ELSE BEGIN; /*Re-throw error*/ /*Rethrow the error. The msg_str parameter will contain the original error information*/ RAISERROR( @MessageTemplate /*msg_str parameter as message format template*/ ,@ErrorSeverity /*severity parameter*/ ,@RaisErrorState /*state parameter*/ ,@ErrorNumber /*argument: original error number*/ ,@ErrorSeverity /*argument: original error severity*/ ,@ErrorState /*argument: original error state*/ ,@ErrorProcedure /*argument: original error procedure name*/ ,@ErrorLine /*argument: original error line number*/ ,@ErrorMessage /*argument: original error message*/ ); RETURN -1; END; /*Re-throw error*/ END /*Stored Procedure*/ GO 他声明错误变量,开始事务,设置保存点,然后在尝试块中执行过程代码。 如果尝试块丢弃错误,则执行将传递到捕获块,该捕获块填充了错误变量。 然后执行从尝试捕获块中脱颖而出。 错误时,事务将回到过程开头的保存点设置。 然后交易提交。 由于SQL Server处理嵌套交易的方式,此提交在另一个事务内执行时仅从交易计数器中减去一个。 (SQL Server中确实不存在嵌套交易。)

sebastian创造了一个非常整洁的模式。 执行链中的每个过程都清理了自己的交易。 不幸的是,这种模式有一个很大的问题:

交易。 注定交易打破了此模式,因为它们无法回归保存点或提交。 他们只能完全回滚。 当然,这意味着您在使用try-catch块时无法在Xact_abort上设置XACT_ABORT(并且您应该始终使用try-catch块。)即使Xact_abort Off,许多错误,例如编译错误,无论如何都会注定交易。 此外,保存点无法与分布式交易一起使用。 我怎么可以解决这个问题? 我需要一个错误的处理模式,该模式将在TSQLT测试框架内工作,还可以在生产中提供一致,正确的错误处理。 我可以在运行时检查环境,并相应地调整行为。 (请参见下面的示例。)但是,我不喜欢。 对我来说感觉就像是一个黑客。 它要求开发环境始终如一地配置。 更糟糕的是,我没有测试我的实际生产代码。 有人有一个出色的解决方案吗? USE TempDB; SET ANSI_NULLS, QUOTED_IDENTIFIER ON; GO IF OBJECT_ID('dbo.ModifiedRollback') IS NOT NULL DROP PROCEDURE dbo.ModifiedRollback; GO CREATE PROCEDURE dbo.ModifiedRollback AS BEGIN; /*Stored Procedure*/ SET NOCOUNT ON; IF RIGHT(@@SERVERNAME, 9) = '\LOCALDEV' SET XACT_ABORT OFF; ELSE SET XACT_ABORT ON; BEGIN TRY; BEGIN TRANSACTION; RAISERROR('This is just a test. Had this been an actual error, we would have given you some cryptic gobbledygook.', 16, 1); COMMIT TRANSACTION; END TRY BEGIN CATCH; IF @@TRANCOUNT > 0 AND RIGHT(@@SERVERNAME,9) <> '\LOCALDEV' ROLLBACK TRANSACTION; THROW; END CATCH; END; /*Stored Procedure*/ GO

Edit:经过进一步的测试,我发现我的修改后的回滚也不起作用。 当该过程丢弃错误时,它会退出而不向后滚动或提交。 tsqlt引发错误,因为@@ trancount当过程启动时,过程退出时与计数不匹配。 经过一些反复试验,我发现了在测试中起作用的解决方法。 它结合了两种错误处理方法 - 使错误交给更为复杂,并且某些代码路径无法测试。 我很想找到一个更好的解决方案。

USE TempDB; SET ANSI_NULLS, QUOTED_IDENTIFIER ON; GO IF OBJECT_ID('dbo.TestedRollback') IS NOT NULL DROP PROCEDURE dbo.TestedRollback; GO CREATE PROCEDURE dbo.TestedRollback AS BEGIN /*Stored Procedure*/ SET NOCOUNT ON; /* Due to the way tSQLt uses transactions and the way SQL Server handles errors, we declare our error-handling * variables here, populate them inside the CATCH block and then do our error-handling after exiting */ DECLARE @ErrorStatus BIT ,@ErrorNumber INT ,@MessageTemplate NVARCHAR(4000) ,@ErrorMessage NVARCHAR(4000) ,@ErrorProcedure NVARCHAR(126) ,@ErrorLine INT ,@ErrorSeverity INT ,@ErrorState INT ,@RaisErrorState INT ,@ErrorLineFeed NCHAR(1) = CHAR(10) ,@FALSE BIT = CAST(0 AS BIT) ,@TRUE BIT = CAST(1 AS BIT) ,@tSQLtEnvironment BIT ,@SavepointName VARCHAR(32) = REPLACE( (CAST(NEWID() AS VARCHAR(36))), '-', ''); /*Savepoint names are 32 characters long and must be unique. UNIQUEIDs are 36, four of which are dashes*/ /* The tSQLt Unit Testing Framework we use in our local development environments must maintain open transactions during testing. So, * we don't roll back transactions during testing. Also, doomed transactions can't stay open, so we SET XACT_ABORT OFF while testing. */ IF RIGHT(@@SERVERNAME, 9) = '\LOCALDEV' SET @tSQLtEnvironment = @TRUE ELSE SET @tSQLtEnvironment = @FALSE; IF @tSQLtEnvironment = @TRUE SET XACT_ABORT OFF; ELSE SET XACT_ABORT ON; BEGIN TRY; SET ROWCOUNT 0; /*The ROWCOUNT setting can be updated outside the procedure and changes its behavior. This sets it to the default.*/ SET @ErrorStatus = @FALSE; BEGIN TRANSACTION; /*We need a save point to roll back to in the tSQLt Environment.*/ IF @tSQLtEnvironment = @TRUE SAVE TRANSACTION @SavepointName; RAISERROR('Cryptic gobbledygook.', 16, 1); COMMIT TRANSACTION; RETURN 0; END TRY BEGIN CATCH; SET @ErrorStatus = @TRUE; /* Build a message string with placeholders for the original error information * Note: "%d" & "%s" are placeholders (substitution parameters) which capture * the values from the argument list of the original error message. */ SET @MessageTemplate = N': Error %d, Severity %d, State %d, ' + @ErrorLineFeed + N'Procedure %s, Line %d, ' + @ErrorLineFeed + N', Message: %s'; SELECT @ErrorMessage = ERROR_MESSAGE() ,@ErrorNumber = ERROR_NUMBER() ,@ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-') ,@ErrorLine = ERROR_LINE() ,@ErrorSeverity = ERROR_SEVERITY() ,@ErrorState = ERROR_STATE() ,@RaisErrorState = CASE ERROR_STATE() WHEN 0 /*RAISERROR Can't generate errors with State = 0*/ THEN 1 ELSE ERROR_STATE() END; END CATCH; /* Due to the way the tSQLt test framework uses transactions, we use two different error-handling schemes: * one for unit-testing and the other for our main Test/Staging/Production environments. In those environments * we roll back transactions in the CATCH block in the event of an error. In unit-testing, on the other hand, * we begin a transaction and set a save point. If an error occurs we roll back to the save point and then * commit the transaction. Since tSQLt executes all test in a single explicit transaction, starting a * transaction at the beginning of this stored procedure just adds one to @@TRANCOUNT. Committing the * transaction subtracts one from @@TRANCOUNT. Rolling back to a save point does not affect @@TRANCOUNT. */ IF @ErrorStatus = @TRUE BEGIN; /*Error Handling*/ IF @tSQLtEnvironment = @TRUE BEGIN; /*tSQLt Error Handling*/ ROLLBACK TRANSACTION @SavepointName; /*Rolls back to save point but does not affect @@TRANCOUNT*/ COMMIT TRANSACTION; /*Subtracts one from @@TRANCOUNT*/ END; /*tSQLt Error Handling*/ ELSE IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; /*Rethrow the error. The msg_str parameter will contain the original error information*/ RAISERROR( @MessageTemplate /*msg_str parameter as message format template*/ ,@ErrorSeverity /*severity parameter*/ ,@RaisErrorState /*state parameter*/ ,@ErrorNumber /*argument: original error number*/ ,@ErrorSeverity /*argument: original error severity*/ ,@ErrorState /*argument: original error state*/ ,@ErrorProcedure /*argument: original error procedure name*/ ,@ErrorLine /*argument: original error line number*/ ,@ErrorMessage /*argument: original error message*/ ); END; /*Error Handling*/ END /*Stored Procedure*/ GO

我对此修改框架过程tsqlt.private_runtest进行修改。 基本上,在主要的捕获块中,它正在尝试进行命名的回滚(对我来说是1448行),我正在替换
    ROLLBACK TRAN @TranName;

IF XACT_STATE() = 1 -- transaction is active ROLLBACK TRAN @TranName; -- execute original code ELSE IF XACT_STATE() = -1 -- transaction is doomed; cannot be partially rolled back ROLLBACK; -- fully roll back IF (@@TRANCOUNT = 0) BEGIN TRAN; -- restart transaction to fulfill expectations below 预测测试看起来不错。 敬请关注。 (在对此提议的编辑获得更多信心后,我将提交Git。)

没有更改TSQLT框架存储的过程,这是我们为使用生产存储过程使用的方法提出的解决方法,同时仍允许TSQLT测试框架能够测试它,而无需收到错误:
SET XACT_ABORT ON


sql-server transactions tsqlt
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.