【问题标题】:Can I use C# locks to prevent SQL Server deadlocks in my web application?我可以使用 C# 锁来防止我的 Web 应用程序中的 SQL Server 死锁吗?
【发布时间】:2013-03-13 02:49:42
【问题描述】:

我正在使用 C# 在 Visual Studio 2010 中编写一个 Web 应用程序。 Web 应用程序执行复杂的 SQL Server 2008 语句,如果同时多次调用同一个 .aspx 页面,有时会导致死锁。建议的解决方案是使用 SQL Server 手段来防止这些死锁,但我的问题是我不太了解它,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

【问题讨论】:

  • 如果您更详细地描述脚本正在做什么以及您认为可能导致死锁的原因,您可能会得到更好的答案。实际的脚本也会有所帮助。
  • @SimonWhitehead:添加了代码示例。谢谢。
  • 如果这可能会导致一个死锁,我不会感到惊讶。您可能希望选择临时表,而不是 CTE,然后从中插入选择。这样,select 和 insert 会锁定不同的表。
  • 顺便说一下,在 ASP.NET 中锁定很棘手。锁定仅限于单个应用程序域的范围,因此根据您的 Web 服务器的情况,其他请求可能会或可能不会看到锁定。此外,它会阻碍 Web 服务器的最佳方面之一,即同时处理多个请求。
  • 我建议在数据库级别解决这个问题。如果您在应用程序层执行此操作,它将根本无法很好地扩展。我会首先检查以确保您的索引设置正确并以最佳水平执行。确保您在事务内部运行。验证任何热点的执行计划,然后开始寻找其他替代方案(尝试/捕获/重试逻辑与死锁等)。其他选项正在考虑更改在表级别使用页面锁定的方式(一种方法是禁用页面锁定并改用行级锁定)。

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


【解决方案1】:

我在 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 所以我想唯一要问的问题是 你想要更多的绳子吗?

【讨论】:

  • 我很欣赏你居高临下的回答,但正如我上面所说,我不太了解 SQL 锁,因此考虑使用我更了解的东西:Windows 内核锁。至于捕获每个人都在重复的死锁图的建议——我没有 SQL Server Profiler 来做到这一点。我所拥有的只是 Visual Studio 2010 附带的 SQL Server express,我无法为 SQL Server 2008 许可证支付 M$ 想要的任何东西......
  • 至于你的情况,当互斥锁锁定 ASP.NET 应用程序时,它也有一个超时,你知道,它比 SQL Server 更易于管理和控制。此外,显然每个对我的 t_Log_2 表的引用都必须包含在同一个互斥锁中,以防止您描述的死锁。这是您首先学习的同步基础。
【解决方案2】:

由于没有足够的细节,找不到真正导致死锁的地方,我猜可能是因为键范围导致死锁,这意味着死锁发生在表 t_log_2 的索引上,因为你有删除和更新,肯定它们不是发生在同一行,但它们可以发生在同一个键范围内,或者一个进程可以持有 A 范围,请求 B 范围,另一个进程可以持有 B 范围并请求 A 范围。您可以使用 SQL Profiler 来跟踪死锁并查看它到底发生在哪里。或者,简单地说,如果它不会对您的性能造成太大影响,您可以将事务隔离级别设置为 [repeatable read] 甚至 [serializable]。


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

【讨论】:

  • 感谢您的分析,但我正在询问使用应用层锁来解决问题...
  • 当然,你当然可以使用应用层来解决死锁,或者我们可以说避免死锁。无论哪种方式都没有偏好,如果它可以解决您的业务需求并达到性能基准,那就很好了。
猜你喜欢
  • 2017-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-02
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多