【发布时间】:2021-03-26 08:17:16
【问题描述】:
我有一个插入数百条记录的查询。查询背后的想法是:
- 删除旧记录
id - 插入具有相同
id的新记录 - 如果
id的记录不存在,将生成eternal_id的值 - 如果
id的记录存在,我们应该保存eternal_id的值 - 查询在事务中执行,Read Committed 类型
查询如下:
DECLARE @id1 int = 100
DECLARE @id2 int = 200
CREATE TABLE #t(
[eternal_id] [uniqueidentifier] NULL,
[id] [int] NOT NULL
)
DELETE FROM [dbo].[SomeTable] WITH (HOLDLOCK)
OUTPUT
DELETED.eternal_id
,DELETED.id
INTO #t
WHERE [id] IN (@id1, @id2)
INSERT INTO [dbo].[SomeTable]
([id]
,[title]
,[eternal_id])
SELECT main.*, ISNULL([eternal_id], NEWID())
FROM
(
SELECT
@id1 Id
,'Some title 1' Title
UNION
SELECT
@id2 Id
,'Some title 2' Title
) AS main
LEFT JOIN #t t ON main.[id] = t.[id]
DROP TABLE #t
我有数百个线程使用不同的@id 执行此查询。当[dbo].[SomeTable] 中的记录已经存在时,一切正常,但是当@id 的记录不存在时,我正在捕捉:
事务(进程 ID 73)在锁定资源上与另一个进程死锁,并已被选为死锁牺牲品。重新运行事务。
因此,当 2 个或更多并发线程通过相同的 @id 并且 [dbo].[SomeTable] 中不存在记录时,就会出现问题。
我试图在这里删除WITH (HOLDLOCK):
DELETE FROM [dbo].[SomeTable] WITH (HOLDLOCK)
OUTPUT
DELETED.eternal_id
,DELETED.id
INTO #t
WHERE [id] IN (@id1, @id2)
这没用,我开始明白了:
违反主键约束“PK__SomeTable__3213E83F5D97F3D0”。无法在对象“dbo.SomeTable”中插入重复键。重复键值为 (49)。 声明已终止。
因此,如果没有 WITH (HOLDLOCK),即使记录已经存在,它也无法正常工作。
当表中不存在id的记录时如何防止死锁?
【问题讨论】:
-
为什么要删除然后插入,而不是更新?更新不需要
HOLDLOCK提示,因为它是唯一有效的操作。 -
1.我不明白为什么这不是更新 2. 不知道你为什么在这里使用临时表,我怀疑表变量所涉及的锁定会更少 3.
WITH (HOLDLOCK, UPDLOCK)有帮助吗? 4.请edit添加表的索引定义,死锁图并通过brentozar.com/pastetheplan分享查询计划 -
虽然尝试避免所有死锁是一个雄心勃勃的目标……但这并不总是可能的……而且您无法阻止所有未来死锁的发生,因为随着更多行添加到表查询计划改变。任何应用程序代码都应该有某种形式的重试机制来处理这个问题。
-
@gvee 感谢您的回复。在幕后,更新是简单的
delete和insert命令,所以仍然会发生死锁。更多@id,死锁的可能性更大。 -
@DaleK 感谢您的回复。我想我明白你在说什么。是的,我想我应该实现某种形式的重试机制来处理这个问题。
标签: sql-server multithreading tsql concurrency deadlock