我可以使用C#锁来防止Web应用程序中出现SQL Server死锁吗?

时间:2013-03-13 02:49:42

标签: c# asp.net sql-server sql-server-2008 locking

我正在使用C#在Visual Studio 2010中编写Web应用程序。 Web应用程序执行复杂的SQL Server 2008语句,如果同时多次调用相同的.aspx页面,有时会导致死锁。建议的解决方案是使用SQL服务器手段来防止这些死锁,但我的问题是我并不是很了解它,这对于C#来说并不是真的,我知道的方式更好。

所以我想知道,我在ASP.NET页面中使用锁(或者是C#中的锁,或者是名为mutex的Windows)而不是通过SQL服务器进行锁定以防止这些死锁,这有什么缺点?

PS。有问题的SQL Server数据库仅由此Web应用程序使用。

编辑:以下是执行SQL语句的C#代码:

int iNumRows = 0;

using (SqlConnection cn = new SqlConnection(strConnection))
{
    cn.Open();

    using (SqlCommand cmd = new SqlCommand(strSQL, cn))
    {
        //Use C# lock here
        iNumRows = Convert.ToInt32(cmd.ExecuteScalar());
        //Release C# lock here
    }
}

这是一个SQL示例(实际上是由C#脚本动态编写的):

SET XACT_ABORT ON;
BEGIN TRANSACTION;

DELETE FROM [dbo].[t_Log_2] 
      WHERE [idtm]<'2011-03-12 08:41:57';

WITH ctx AS(
     SELECT MIN([idtm]) AS mdIn, 
            MAX([odtm]) AS mdOut 
           FROM [dbo].[t_Log_2] 
          WHERE [type] = 0 
            AND [state] = 0 
            AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
            AND [odtm] >= '2013-03-11 06:33:32' 
            AND [idtm] <= '2013-03-11 06:43:12' 
           ) 
INSERT INTO [dbo].[t_Log_2] 
([oid],[idtm],[odtm],[type],[state],[huid],
 [cnm],[cmdl],[batt],[dvtp0],[dvtp1]) 
SELECT 
    2, 
    CASE WHEN mdIn IS NOT NULL 
          AND mdIn < '2013-03-11 06:33:32' 
         THEN mdIn 
         ELSE '2013-03-11 06:33:32' 
         END,
    CASE WHEN mdOut IS NOT NULL 
          AND mdOut > '2013-03-11 06:43:12' 
         THEN mdOut 
         ELSE '2013-03-11 06:43:12' 
         END,
    0,
    0,
    N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4',
    null,
    null,
    0,
    1,
    null 
FROM ctx 

SELECT ROWCOUNT_BIG()

DELETE FROM [dbo].[t_Log_2] 
      WHERE [type] = 0 
        AND [state] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [odtm] >= '2013-03-11 06:33:32' 
        AND [idtm] <= '2013-03-11 06:43:12' 
        AND [id] <> SCOPE_IDENTITY()

DELETE FROM [dbo].[t_Log_2] 
      WHERE [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] >= (SELECT [idtm] FROM [dbo].[t_Log_2] 
                                    WHERE [id] = SCOPE_IDENTITY()) 
        AND [odtm] <= (SELECT [odtm] FROM [dbo].[t_Log_2] 
                                    WHERE [id] = SCOPE_IDENTITY()) 
        AND [id] <> SCOPE_IDENTITY() 

;WITH ctx1 AS( 
     SELECT [idtm] AS dI 
       FROM [dbo].[t_Log_2] 
      WHERE [id] = SCOPE_IDENTITY() 
             )
UPDATE [dbo].[t_Log_2] 
        SET [odtm] = ctx1.dI 
       FROM ctx1 
      WHERE [id] <> SCOPE_IDENTITY() 
        AND [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] < ctx1.dI 
        AND [odtm] > ctx1.dI 

;WITH ctx2 AS(
     SELECT [odtm] AS dO 
       FROM [dbo].[t_Log_2] 
      WHERE [id] = SCOPE_IDENTITY() 
             ) 
UPDATE [dbo].[t_Log_2] 
        SET [idtm] = ctx2.dO 
       FROM ctx2 
      WHERE [id] <> SCOPE_IDENTITY() 
        AND [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] < ctx2.dO 
        AND [odtm] > ctx2.dO 

COMMIT TRANSACTION;
SET XACT_ABORT OFF

2 个答案:

答案 0 :(得分:7)

  

我使用ASP.NET页面中的锁(或C#中的锁,或者名为mutex的Windows)而不是通过SQL服务器进行锁定来防止这些死锁,这有什么缺点?

不会导致死锁,而是会造成活锁。

当等待图包含一个循环(A等待B,B等待A)时发生死锁。 SQL Server定期检查所有等待图并查找周期。当检测到一个这样的循环时,通过选择受害者并中止它的交易来打破循环。

如果您将其中一些锁移到SQL Server控制的域之外,即。在进程互斥体,关键部分,C#事件或其他什么时候,等待图循环仍然会发生,但现在循环将通过应用程序完成,因此SQL Server将无法检测到(A在SQL中等待B,但是B等待A在应用程序中)。由于死锁监视器不会看到循环,它将不会运行死锁解析算法(选择受害者,中止它的事务)并且死锁将永远保持。恭喜,现在您的应用程序只是挂起而不是引发死锁异常!

你不必接受我的话,其他更有经验的人已经被这个问题烧掉并且学到了很多东西,但幸运的是写了这篇文章,这样你就可以学到简单的方法。 This very site you're reading is an example

一旦理解了问题,解决SQL Server中的死锁就相当容易了。如果您capture and attach the deadlock graph(XML,而不是图片!),以及表格的确切定义,也许我们可以提供帮助。唉,you already ignored such request所以我想唯一要问的问题是你想要更多绳子吗?

答案 1 :(得分:1)

如果不了解足够的细节,找不到真正导致死锁的地方,我猜可能是因为键范围导致了死锁,这意味着表t_log_2的索引发生了死锁,因为你有删除和更新,绝对是它们不会发生在同一行,但它们可能发生在相同的键范围内,或者一个进程可以保持A范围,请求B范围,另一个进程可以保持B范围并请求A范围。您可以使用SQL事件探查器跟踪死锁并查看其确切发生的位置。或者,简单地说,如果它不会对您的性能造成太大影响,您可以将事务隔离级别设置为[可重复读取]甚至[可序列化]。


SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
....
BEGIN TRANSACTION
....