【问题标题】:Single Table Stored Procedure Deadlocks单表存储过程死锁
【发布时间】:2017-02-20 08:03:17
【问题描述】:

我正在尝试增加 MSSQL 中的值(例如,在购买礼品卡后增加用户的余额)。

我的存储过程和表如下:

CREATE TABLE Test_Table ([intCount] [int] NOT NULL)

ALTER PROCEDURE Test_Proc AS
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRAN
        UPDATE Test_Table
        SET intCount = intCount + 1 
    COMMIT TRAN
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

    RETURN(0)
END

为简单起见,我在表中只有一行,我只是递增所有行。

我在 C# 中生成 10 个线程,并在每个线程中调用存储过程 10 次。但是,我在大多数线程中都遇到了死锁。我调用此存储过程的代码如下所示:

for (int thread = 0; thread < threads; thread++)
{
    new Thread(() =>
    {
        try
        {
            for (int ix = 0; ix < count; ix++)
            {
                using (var conn = new SqlConnection(connectionString))
                {
                    conn.Open();
                    SqlCommand cmd = new SqlCommand("Test_Proc", conn);
                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.ExecuteNonQuery();
                }
            }
        }
        catch (Exception e)
        {
            errors++;
        }
    }).Start();
}

我尝试了 WITH (UPDLOCK, HOLDLOCK),但这似乎并没有减少死锁的频率。

无论如何我可以更改存储过程来防止这些死锁吗?我真的在寻找一个 SQL 答案,而不是仅仅在 C# 中序列化所有存储过程调用。

(这类似于Deadlock with single stored procedure and multiple threads,但是该问题明确询问如何死锁,而我只是想避免死锁)。

编辑:我修改了代码以将存储过程的内容放入事务中,但它仍然死锁。

编辑:错误消息类似于Transaction (Process ID 124) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

编辑:我根据反馈更新了存储过程,但它仍然死锁。

编辑:看起来解释在:https://stackoverflow.com/a/36831413/1117119。 SERIALIZABLE 获得一个共享锁,然后在要写入时将其转换为排他锁。这会导致死锁。

似乎还会发生任何进一步的死锁,因为我的线程太多:SQL Server 2008: Getting deadlocks... without any locks

【问题讨论】:

  • 检查这个 q/a (stackoverflow.com/a/41594231/1158842) 似乎是同一个根本问题。如果您希望在 SQL 级别完成,那么您可能会前往 sp_getapplock
  • 不需要先select当前值。 UPDATE Test_Table SET intCount = intCount + 1 就足够了。当你说“it deadlocks”时,你是否真的得到一个异常,因为 SQL Server 检测到死锁而导致一个事务被终止?如果是,那么请将该异常的错误消息添加到您的问题中。预计线程会等待,因为您不能同时从多个事务中更新同一行,第二次更新总是需要等待前一次完成。

标签: sql sql-server


【解决方案1】:

我知道您向我们展示的只是问题的简化版本,但无论如何请考虑是否可以将其作为单独的 UPDATE 而不是单独的 SELECT 和 UPDATE,即:

UPDATE Test_Table SET intCount += 1  

如果这不可能,则将两个语句包含在一个事务中。

编辑: 为避免转换锁死锁,请在 SELECT 语句中添加 with(xlock, tablock) 提示。

【讨论】:

  • 感谢您的回复。我更改了代码以将存储过程的主体放入事务中,但它仍然像以前一样经常死锁。
【解决方案2】:

Dean 的回答是最正确的,但如果其他人想知道如何使单独的 SELECT 和 UPDATE 正常工作,这是我最终得到的代码:

BEGIN TRAN

    DECLARE @x int

    select @x = intCount
    from Test WITH (TABLOCKX, UPDLOCK)
    WHERE id = 1    

    UPDATE Test
    SET intCount = @x + 1
    WHERE id = 1    

COMMIT TRAN

这解决了第一个选择不采取排他锁的问题 (https://stackoverflow.com/a/36831413/1117119)。

此外,如果创建的线程过多,任何获取锁的代码都会死锁 (SQL Server 2008: Getting deadlocks... without any locks)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-24
    • 1970-01-01
    • 2011-10-22
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 1970-01-01
    • 2014-11-27
    相关资源
    最近更新 更多