我最近编写了一个 T-SQL 脚本来对 3 个不同的表执行一些更新和插入操作。我希望它在单个事务中完成,因此我阅读了有关如何使用 try/catch 的显式事务的 Microsoft 文档。
根据文档,可以这样做:
BEGIN TRANSACTION;
BEGIN TRY
-- Generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO
所以我实现了这个模式并将我所有的更新和插入都放在了 TRY 子句中。问题是,代码在查询完成后设法留下了一个打开的事务,我不知道这是怎么发生的。不管在 TRY 子句中做了什么工作,有哪些可能的场景会导致这样的查询使事务保持打开状态?
有几种类型的错误没有被 T-SQL
TRY
/CATCH
结构捕获(取自TRY...CATCH的 MSDN 页面,为清楚起见稍作编辑):对于问题中提供的示例代码(即单个 DML 语句),有一个非常简单的解决方法:删除显式事务。对于单个 DML 语句,显式事务是不必要的,除非有额外的逻辑对某些条件进行检查,并且可以选择通过
ROLLBACK
main 中的 doneTRY
而不是作为一部分来撤消该更改,CATCH
因为它不是数据库引擎错误。在该特定场景之外,我看不出有理由(至少是一个好理由)为单个 DML 语句使用显式事务。但我仍然会保留TRY
/CATCH
结构以进行一般错误处理。当涉及到多个 DML 语句时(这是问题的真实上下文,如示例代码上方的措辞中所述),那么您显然确实需要显式事务,因此这里有一些想法:
sys.dm_exec_connections
.连接池使自动回滚处理变得复杂,因为它在设计上保持连接打开,即使在客户端“关闭”它之后也是如此。当连接保持打开以便另一个“连接”尝试可以简单地重新使用它时,会话本身不会“清理”,直到打开新连接并执行查询批处理!在应用程序代码在实际重用池中的连接的“新”连接上提交的第一次执行时
sp_reset_connection
,将调用一个标记为的内部进程,除其他外,该进程将回滚该会话中的未提交事务,即现在被重复使用。这里的问题是当使用连接池时,进程终止(可能是命令超时),并且没有新的连接被请求,也没有新的查询被提交。在这种情况下,连接只是位于池中,会话仍然存在,并且没有请求清理操作。但这不会永远持续下去(尽管在这些情况下仍然比你想要的长)。根据SQL Server Connection Pooling (ADO.NET)的 MSDN 页面(在删除连接部分):CATCH
块相同的效果。但是在 SSMS 中取消执行仍然会让您保持在同一个会话中,因此事务仍然处于活动状态,直到您手动调用COMMIT
orROLLBACK
,或者直到您关闭该查询选项卡(此时它会告诉您有一个未提交的事务并询问是否你想提交还是不提交;我的测试表明回答“不”会回滚)。那么如何处理使用连接池的应用程序代码呢?我不是使用
SET XACT_ABORT ON;
. 我首先要尝试的是:Max Pool Size
Connections 可以通过连接字符串关键字设置连接池的最大大小。如果池非常大,那么任何特定连接都不太可能被快速重用。默认池大小似乎是 100。将值设置为略低于当前值(但不要太低以免无法有效利用连接池)将有助于确保更快地重用连接将意味着清理进程将被更快地调用,这将回滚任何打开的事务。在您正在执行查询/存储过程的应用程序代码中,
catch
您可以在块中调用SqlConnection.ClearPool,它应该关闭 SQL Server 级别的实际连接,这反过来应该允许自动回滚未提交的事务。您甚至可以通过检查异常以查看它是否是命令超时或其他几个会在跳过 T-SQLCATCH
块时终止进程的场景之一,如果是,则调用ClearPool
.从技术上讲,您可以
Pooling=false;
通过在连接字符串中指定来完全禁用连接池。但是,我不认为这是必要的,并且不推荐它,除非绝对必要或拥有一个不会产生大量连接的应用程序。导致事务保持打开的常见情况是命令超时。命令超时发生在客户端,而不是服务器上。当查询运行时间超过指定时间
CommandTimeout
时,客户端 API 会向服务器发送一条注意命令以取消批处理。这可以防止批处理中的任何后续代码(包括CATCH
块)被执行。SET XACT_ABORT ON
除非指定,否则事务将保持打开状态。SET XACT_ABORT ON
设置会导致事务在出现 T-SQL 错误后回滚,因此我建议您对所有显式事务都养成这种习惯。以下是我推荐的一般模式示例。THROW;
在 SQL Server 2012 及更高版本中使用CATCH
块而不是RAISERROR
丑陋。请注意,同一范围内的编译错误不会受到影响
SET XACT_ABORT
,也无法被捕获。可能的场景:
解析(语法和语义)
编译(无效的对象引用,如果语句被缓存和标记但不是第一次,错误将命中 catch 块)
命令超时