SQL:死锁,因为来自子表的DELETE需要外键父表上的XLOCK

时间:2016-09-20 13:54:24

标签: sql-server

我在外键关系中有两个表,如下所示:

CREATE TABLE table_a 
(
     id int IDENTITY(1,1) NOT NULL PRIMARY KEY
)

CREATE TABLE table_b 
(
    id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
    a_id int FOREIGN KEY REFERENCES table_a(id),
    other_id int NOT NULL,
    info varchar(max) NOT NULL
)

table_b的索引位于a_idother_id

CREATE NONCLUSTERED INDEX table_b_i1 
ON table_b (a_id ASC, other_id ASC)

两个线程以3个步骤运行事务:

  1. 创建一个临时#table_b来保存table_b
  2. 的一些新值
  3. table_b中删除,其中a_id#table_b的值相匹配但other_id不符合
  4. 合并到table_b,其中a_idother_id都匹配来自#table_b
  5. 的值

    交易:

    -- step 1
    SELECT /* some stuff */ INTO #table_b
    
    -- step 2
    DELETE FROM table_b
    WHERE EXISTS (SELECT 1 FROM #table_b tb_t WHERE tb_t.a_id = table_b.a_id) AND
      NOT EXISTS (SELECT 1 FROM #table_b tb_t WHERE tb_t.a_id = table_b.a_id AND tb_t.other_id = table_b.other_id)
    
    -- step 3
    MERGE INTO table_b AS target USING #table_b AS source
    ON target.a_id = source.a_id AND target.other_id = source.other_id
    WHEN MATCHED THEN
    UPDATE SET
      target.info = source.info
    WHEN NOT MATCHED BY target THEN
    INSERT (a_id, other_id, info)
    VALUES (source.a_id, source.other_id, source.info)
    

    当一个线程正在运行第2步而另一个线程正在运行第3步时发生死锁。非常重要的是要注意在任何时候是一个使用相同线程的线程{{ 1}}作为另一个;也就是说,一个线程中的临时a_id永远不会匹配来自另一个线程#table_b中的单个a_id。这是死锁图的样子:

    #table_b

    第一个页面锁是完全有意义的 - 合并(步骤3)正在更新table_b中的很多行,因此它有一个U页锁,而删除(步骤2)是从table_b中删除单个行,所以它&#39 ; s试图获得一个IU页面锁。

    第二个对我毫无意义:确定,合并(步骤3)应该尝试读取包含<deadlock> <victim-list> <victimProcess id="process_step2" /> </victim-list> <process-list> <process id="process_step2" ...> <executionStack> ... </executionStack> <inputbuf> /* STEP 2 */ </inputbuf> </process> <process id="process_step3" ...> <executionStack> ... </executionStack> <inputbuf> /* STEP 3 */ </inputbuf> </process> </process-list> <resource-list> <pagelock ... subresource="FULL" objectname="table_b" ... mode="U" associatedObjectId==>(table_b.id)> <owner-list> <owner id="process_step3" mode="U" /> </owner-list> <waiter-list> <waiter id="process_step2" mode="IU" requestType="wait" /> </waiter-list> </pagelock> <pagelock ... subresource="FULL" objectname="table_a" ... mode="IX" associatedObjectId==>(table_b_i1)> <owner-list> <owner id="process_step2" mode="IX" /> </owner-list> <waiter-list> <waiter id="process_step3" mode="S" requestType="wait" /> </waiter-list> </pagelock> </resource-list> </deadlock> 的索引,以满足a_id上的外键约束;但为什么删除(第2步)完全依赖table_a.id?我能理解为什么插入或更新需要确保满足相同的约束,但为什么要删除?第2步不会引用table_a,尤其不会引用table_a

    我无法理解为什么此事务导致死锁,尤其不是为什么外键约束的子表中的删除应该要求对父表进行锁定。有什么想法吗?

1 个答案:

答案 0 :(得分:0)

尝试提供步骤2:从table_b with(tablock)中删除.........

然后,在您完成第3步并提交之前,其他任何过程都不会运行第2步。

如果您的流程没有事务运行,则应在两个语句中都使用“ with(tablock)”或添加“ begin tran”和“ commit”