97

从 .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.

但是,它似乎不是此异常的记录错误代码

针对消息中存在的死锁关键字过滤异常似乎是实现此行为的一种非常丑陋的方式。有人知道这样做的正确方法吗?

4

3 回答 3

163

死锁的特定于 Microsoft SQL Server 的错误代码是 1205,因此您需要处理 SqlException 并检查它。因此,例如,如果对于所有其他类型的 SqlException 您希望将异常冒泡:

catch (SqlException ex)
{
    if (ex.Number == 1205)
    {
        // Deadlock 
    }
    else
        throw;
}

或者,使用 C# 6 中提供的异常过滤

catch (SqlException ex) when (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 中实现死锁重试逻辑。

于 2010-02-13T08:31:17.447 回答
48

因为我想你可能想检测死锁,以便能够重试失败的操作,所以我想警告你一个小问题。我希望你能原谅我在这里有点跑题了。

数据库检测到的死锁将有效地回滚您正在运行的事务(如果有),而连接在 .NET 中保持打开状态。重试该操作(在同一连接中)意味着它将在无事务上下文中执行,这可能导致数据损坏。

意识到这一点很重要。最好考虑完整的连接,以防SQL导致失败。重试操作只能在定义事务的级别上完成(通过重新创建该事务及其连接)。

所以当你重试失败的操作时,请确保你打开一个全新的连接并开始一个新的事务。

于 2010-02-13T13:26:11.630 回答
5

这是检测死锁的 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命令由于死锁而失败时,它会导致事务回滚,如果你不重新创建事务,你的重试将在上下文之外执行交易,并可能导致数据不一致。

于 2017-03-21T22:14:14.530 回答