我在外键关系中有两个表,如下所示:
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_id
和other_id
:
CREATE NONCLUSTERED INDEX table_b_i1
ON table_b (a_id ASC, other_id ASC)
两个线程以3个步骤运行事务:
#table_b
来保存table_b
table_b
中删除,其中a_id
与#table_b
的值相匹配但other_id
不符合table_b
,其中a_id
和other_id
都匹配来自#table_b
交易:
-- 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
。
我无法理解为什么此事务导致死锁,尤其不是为什么外键约束的子表中的删除应该要求对父表进行锁定。有什么想法吗?
答案 0 :(得分:0)
尝试提供步骤2:从table_b with(tablock)中删除.........
然后,在您完成第3步并提交之前,其他任何过程都不会运行第2步。
如果您的流程没有事务运行,则应在两个语句中都使用“ with(tablock)”或添加“ begin tran”和“ commit”