如何捕获由死锁引起的SqlException?
从.NET 3.5 / C#应用程序,我想赶上SqlException
但只有当它是由 SQL Server 2008实例上的死锁引起的 。
典型的错误消息是Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
但是,这个exception似乎没有logging在错误代码中。
在消息中过滤出现死锁关键字exception似乎是一种非常难看的方式来实现这种行为。 有人知道这样做的正确方法吗?
Microsft SQL Server特定的死锁的错误代码是1205,所以你需要处理SqlException并检查。 所以,例如,如果对于所有其他types的SqlException,您希望冒泡例外:
catch (SqlException ex) { if (ex.Number == 1205) { // Deadlock } else throw; }
或者,使用C#6中的exception过滤
catch (SqlException ex) where (ex.Number == 1205) { // Deadlock }
查找给定消息的实际SQL错误代码的一个方便的方法是查看SQL Server中的sys.messages。
例如
SELECT * FROM sys.messages WHERE text LIKE '%deadlock%' AND language_id=1033
处理死锁(来自SQL Server 2005及更高版本)的另一种方法是使用TRY … CATCH支持在存储过程中执行此操作:
BEGIN TRY -- some sql statements END TRY BEGIN CATCH IF (ERROR_NUMBER() = 1205) -- is a deadlock ELSE -- is not a deadlock END CATCH
在MSDN中有一个完整的例子,说明如何在SQL中完全实现死锁重试逻辑。
因为我想你可能想要检测死锁,为了能够重试失败的操作,我喜欢警告你一些小问题。 我希望你能原谅我这里的一些话题。
数据库检测到的死锁将有效地回滚您正在运行的事务(如果有的话),同时连接在.NET中保持打开状态。 重试该操作(在同一连接中),意味着它将在无事务的环境中执行,这可能导致数据损坏。
意识到这一点很重要。 最好考虑在SQL引起失败的情况下完整的连接。 只能在定义事务级别(通过重新创build事务及其连接)上重试操作。
所以当你重试失败的操作时,请确保你打开一个全新的连接并开始一个新的事务。
这是检测死锁的C#6方法。
try { //todo: Execute SQL. //IMPORTANT, if you used Connection.BeginTransaction(), this try..catch must surround that code. You must rollback the original transaction, then recreate it and re-run all the code. } catch (SqlException ex) when (ex.Number == 1205) { //todo: Retry SQL }
确保这try..catch围绕你的整个交易。 根据@Steven(详见他的回答),当sql命令由于死锁而失败时,会导致事务回滚,如果不重新创build事务,则重试将在交易并可能导致数据不一致。