SQL 服务器查询 - 为什么我遇到死锁?
SQL Server query - why am I getting deadlock?
我有以下代码:
set transaction isolation level read committed; --this is for clarity only
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:10'
UPDATE dbo.JobDetails
SET ExecutionState_Status = 10
WHERE JobName = @jobName
COMMIT
和第二个几乎相同的部分:
set transaction isolation level read committed;
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:15'
UPDATE dbo.JobDetails
SET ExecutionState_Status = 20
WHERE JobName = @jobName
COMMIT
区别在于我们设置的状态(10 对 20)和延迟(10 对 15 秒)。
我在 Management Studio 中并行执行它们 - 两个选项卡。现在的问题 - 读取提交的事务隔离级别按预期工作 - 最后修改被应用并且两个脚本都成功执行。
但这不是我想要的 - 我只想执行一个,而第二个什么也不做。这就是为什么我试图将级别更改为可重复读取。据我所知(我现在想挑战一下)它应该是这样的:
- 第一个事务启动并锁定它读取的行
- 第一笔交易等待 10 秒
- 第二个事务同时开始并且无法执行 select 因为它被第一个
锁定
- 第一个事务完成等待,更新 table 并提交
- 第二个事务然后可以继续并且不执行任何操作,因为状态 = 1 的所有行都已更新
不幸的是,我看到的结果远非如此 - 交易陷入僵局,其中一个交易被 SQL 服务器终止。我真的不明白为什么会这样,因为他们以相同的顺序访问资源。
测试需要的脚本如下:
CREATE TABLE [dbo].[JobDetails](
[JobName] [nvarchar](128) NOT NULL,
[ExecutionState_Status] [int] NULL DEFAULT ((0)),
CONSTRAINT [PK_dbo.JobDetails] PRIMARY KEY CLUSTERED
(
[JobName] ASC
))
GO
INSERT INTO JobDetails VALUES( 'My Job', 1)
UPDATE JobDetails SET ExecutionState_Status = 1
补充说明:
- 我正在 table.
中仅用一行进行测试
- 将级别更改为可序列化也会导致死锁。
- 这段代码之所以看起来像这样是因为我正在尝试模拟 ORM 将要执行的操作 - 首先获取实体,然后在状态为 1 时检查代码,然后使用 [= 发送更新14=]基于PK。我知道我可以在没有 ORM 更新
WHERE ExecutionState_Status = 1
的情况下编写代码
我得到了一个 link 来回答这里发生的事情。该示例与我的示例几乎相同,因此我不会在此处复制它。
现在引述解释:
A second type of deadlock can occur with the isolation level
Repeatable Read if you read data with the intention to update it
later. Let’s have a look at the T-SQL code of a simple transaction.
To cause this type of deadlock you just need to run the transaction
across multiple sessions. You even don’t need to access different data
ranges as you can see from the code. Let’s try to explain what happens
here. When this transaction runs across multiple sessions
concurrently, all sessions can acquire the Shared Locks for reading
the data.
Because you hold the Shared Locks until the end of the transaction
(COMMIT or ROLLBACK) in Repeatable Read, the following UPDATE
statement can’t acquire the necessary Update Locks, because they are
already blocked by the Shared Locks acquired in the different session.
Deadlock!
以及解决方案 - 将 WITH (UPDLOCK)
添加到第一个 select:
SELECT @jobName = JobName
FROM dbo.JobDetails WITH (UPDLOCK)
WHERE ExecutionState_Status = 1
现在我需要考虑如何将该解决方案应用于 ORM..
这个假设是错误的:
second transaction starts in the meantime and cannot execute the
select since it's locked by the first one
两个 repeatable read
事务的 select
都获取并持有 S
密钥锁,直到 commit
。 S
锁兼容。当 update
试图获得与 S
锁不兼容的 X
锁时,它们会陷入僵局。
与此相反,read commited
事务中的 select
立即释放 S
锁。
使用 exec sp_lock 来查看锁,例如
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:10'
exec sp_lock 58,57
UPDATE dbo.JobDetails
SET ExecutionState_Status = 10
WHERE JobName = @jobName
COMMIT
我有以下代码:
set transaction isolation level read committed; --this is for clarity only
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:10'
UPDATE dbo.JobDetails
SET ExecutionState_Status = 10
WHERE JobName = @jobName
COMMIT
和第二个几乎相同的部分:
set transaction isolation level read committed;
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:15'
UPDATE dbo.JobDetails
SET ExecutionState_Status = 20
WHERE JobName = @jobName
COMMIT
区别在于我们设置的状态(10 对 20)和延迟(10 对 15 秒)。
我在 Management Studio 中并行执行它们 - 两个选项卡。现在的问题 - 读取提交的事务隔离级别按预期工作 - 最后修改被应用并且两个脚本都成功执行。
但这不是我想要的 - 我只想执行一个,而第二个什么也不做。这就是为什么我试图将级别更改为可重复读取。据我所知(我现在想挑战一下)它应该是这样的:
- 第一个事务启动并锁定它读取的行
- 第一笔交易等待 10 秒
- 第二个事务同时开始并且无法执行 select 因为它被第一个 锁定
- 第一个事务完成等待,更新 table 并提交
- 第二个事务然后可以继续并且不执行任何操作,因为状态 = 1 的所有行都已更新
不幸的是,我看到的结果远非如此 - 交易陷入僵局,其中一个交易被 SQL 服务器终止。我真的不明白为什么会这样,因为他们以相同的顺序访问资源。
测试需要的脚本如下:
CREATE TABLE [dbo].[JobDetails](
[JobName] [nvarchar](128) NOT NULL,
[ExecutionState_Status] [int] NULL DEFAULT ((0)),
CONSTRAINT [PK_dbo.JobDetails] PRIMARY KEY CLUSTERED
(
[JobName] ASC
))
GO
INSERT INTO JobDetails VALUES( 'My Job', 1)
UPDATE JobDetails SET ExecutionState_Status = 1
补充说明:
- 我正在 table. 中仅用一行进行测试
- 将级别更改为可序列化也会导致死锁。
- 这段代码之所以看起来像这样是因为我正在尝试模拟 ORM 将要执行的操作 - 首先获取实体,然后在状态为 1 时检查代码,然后使用 [= 发送更新14=]基于PK。我知道我可以在没有 ORM 更新
WHERE ExecutionState_Status = 1
的情况下编写代码
我得到了一个 link 来回答这里发生的事情。该示例与我的示例几乎相同,因此我不会在此处复制它。
现在引述解释:
A second type of deadlock can occur with the isolation level Repeatable Read if you read data with the intention to update it later. Let’s have a look at the T-SQL code of a simple transaction.
To cause this type of deadlock you just need to run the transaction across multiple sessions. You even don’t need to access different data ranges as you can see from the code. Let’s try to explain what happens here. When this transaction runs across multiple sessions concurrently, all sessions can acquire the Shared Locks for reading the data.
Because you hold the Shared Locks until the end of the transaction (COMMIT or ROLLBACK) in Repeatable Read, the following UPDATE statement can’t acquire the necessary Update Locks, because they are already blocked by the Shared Locks acquired in the different session. Deadlock!
以及解决方案 - 将 WITH (UPDLOCK)
添加到第一个 select:
SELECT @jobName = JobName
FROM dbo.JobDetails WITH (UPDLOCK)
WHERE ExecutionState_Status = 1
现在我需要考虑如何将该解决方案应用于 ORM..
这个假设是错误的:
second transaction starts in the meantime and cannot execute the select since it's locked by the first one
两个 repeatable read
事务的 select
都获取并持有 S
密钥锁,直到 commit
。 S
锁兼容。当 update
试图获得与 S
锁不兼容的 X
锁时,它们会陷入僵局。
与此相反,read commited
事务中的 select
立即释放 S
锁。
使用 exec sp_lock 来查看锁,例如
DECLARE @jobName nvarchar(128);
BEGIN TRAN
SELECT @jobName = JobName
FROM dbo.JobDetails
WHERE ExecutionState_Status = 1
WAITFOR DELAY '00:00:10'
exec sp_lock 58,57
UPDATE dbo.JobDetails
SET ExecutionState_Status = 10
WHERE JobName = @jobName
COMMIT