【问题标题】:Deadlock while updating the same table - SQL更新同一张表时发生死锁 - SQL
【发布时间】:2020-01-26 02:49:11
【问题描述】:

有一个存储过程可以更新所有关联的 pre_plan 和 pre_type。而且是死锁。

访问和关联表的DDL:

CREATE TABLE [dbo].[Associate](
    [pre_plan_id] [smallint] NULL,
    [pre_type_id] [smallint] NULL,
    [associate_id] [smallint] NOT NULL,
    [deleted] [bit] NOT NULL
)  

INSERT INTO Associate 
VALUES 
(NULL,  NULL,   -32768, 0),
(NULL,  NULL,   2,  1),
(NULL,  NULL,   3,  0),
(NULL,  NULL,   6,  1),
(NULL,  NULL,   3097,   1),
(NULL,  NULL,   3109,   0),
(NULL,  NULL,   3265,   1),
(NULL,  NULL,   3313,   0),
(NULL,  NULL,   3318,   1),
(NULL,  NULL,   3329,   0)


CREATE TABLE [dbo].[Visit](
    [type_id] [smallint] NOT NULL,
    [plan_id] [smallint] NOT NULL,
    [associate_id] [smallint] NOT NULL,
    [time_in] [smalldatetime] NOT NULL
) 
INSERT INTO Visit 
VALUES
(390,   31, 3109,   '2009-09-02'),
(304,   32, 3109,   '2010-02-05'),
(388,   31, 3109,   '2010-09-24'),
(388,   31, 3109,   '2010-09-27'),
(388,   31, 3109,   '2010-09-27'),
(388,   31, 3109,   '2010-09-28'),
(388,   31, 3109,   '2010-10-01'),
(333,   28, 3109,   '2011-01-11'),
(338,   30, 3109,   '2011-01-18'),
(388,   31, 3109,   '2011-01-27')

存储过程

CREATE PROCEDURE [dbo].[update_pre__] 

AS

UPDATE Associate SET pre_plan_id = 
      (SELECT TOP 1 plan_id 
         FROM Visit  
        WHERE associate_id = Associate.associate_id 
          AND time_in > 90
        GROUP BY plan_id 
        ORDER BY Count(*) DESC)
 WHERE deleted = 0

UPDATE Associate SET pre_type_id = 
      (SELECT TOP 1 [type_id] 
         FROM Visit 
        WHERE associate_id = Associate.associate_id 
          AND time_in > 90
        GROUP BY [type_id] 
        ORDER BY Count(*) DESC)
 WHERE deleted = 0 

我正在考虑将在两个更新语句中添加 BEGIN TRANSACTIONCOMMIT TRANSACTION 的事务分开。是否有助于避免僵局?任何人都可以帮助我提出避免僵局的最有效方法吗?

【问题讨论】:

  • 虽然将两个update 语句包装在一个事务中可能是有意义的,因为它们是按顺序执行的,它们不会相互死锁。还有什么使用AssociateVisit 在运行?

标签: sql tsql sql-update deadlock database-deadlocks


【解决方案1】:

Meybe 在使用SELECT CRUD 操作时使用WITH (NOLOCK) 是个思路

其含义,使用READ UNCOMMITED作为SELECT查询的事务隔离级别。

With (NOLOCK) 引用表的当前状态并忽略 当前操作上的其他 crud 操作。 重要:您可能会错过所选表中不同事务的未提交操作脏读

(SELECT TOP 1 [type_id] 
   FROM Visit With (NOLOCK)
  WHERE associate_id = Associate.associate_id 
    AND time_in > 90
GROUP BY [type_id] 
ORDER BY Count(*) DESC)

【讨论】:

  • 允许脏读 usimg READ UNCOMMITTED 或 NOLOCK 意味着您不再保证获得正确的结果。除非客户端/最终用户另有说明,否则假设需要正确的结果是一种很好的做法。这里有几篇文章更详细地解释了这一点。 brentozar.com/archive/2018/10/…mssqltips.com/sqlservertip/3172/…
  • @AlanBurstein 我完全同意。我说 readuncommitted 操作并忽略其他操作。我正在纠正它以更具描述性
【解决方案2】:

此过程不会产生死锁,因为两个更新语句将按顺序执行。当您创建并运行以下过程 (update_pre__test) 时,添加了 BEGIN TRAN/COMMIT TRAN 块。您可以检查以下 DMV 查询,该查询在另一个查询窗口中显示未结交易。 DMV 查询仅显示一个与此存储过程相关的打开事务。

但是,我们必须注意一想到死锁,我们需要两个打开的事务,它们之间应该存在资源争用。

会话 1:

CREATE  [dbo].[update_pre__test]
AS BEGIN TRAN;
       UPDATE Associate
         SET pre_plan_id = (SELECT TOP 1 plan_id
                            FROM Visit
                            WHERE associate_id = Associate.associate_id
                                  AND time_in > 90
                            GROUP BY plan_id
                            ORDER BY COUNT(*) DESC)
       WHERE deleted = 0;
       UPDATE Associate
         SET pre_type_id = (SELECT TOP 1 [type_id]
                            FROM Visit
                            WHERE associate_id = Associate.associate_id
                                  AND time_in > 90
                            GROUP BY [type_id]
                            ORDER BY COUNT(*) DESC)
       WHERE deleted = 0;
       WAITFOR DELAY '00:01:02';
       COMMIT TRAN

会话 2:

    SELECT *
     FROM sys.dm_tran_active_transactions tat
     INNER JOIN sys.dm_exec_requests  er ON tat.transaction_id = er.transaction_id
    CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-02
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 2020-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多