SQL Server查询 - 为什么我会遇到死锁?

kub*_*003 6 sql sql-server transaction-isolation

我有以下代码:

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 
Run Code Online (Sandbox Code Playgroud)

第二件几乎是相同的:

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 
Run Code Online (Sandbox Code Playgroud)

不同之处在于我们设定的状态(10对20)和延迟(10对15对).

我在Management Studio中并行执行它们 - 两个选项卡.现在问题 - 读取已提交事务隔离级别按预期工作 - 应用最后一次修改并且两个脚本都成功执行.

然而,这不是我想要的 - 我只想执行一个而第二个应该什么都不做.这就是我尝试将级别更改为REPEATABLE READ的原因.根据我的知识(我现在想要挑战),它应该像这样:

  • 第一个事务启动并锁定它读取的行
  • 然后第一笔交易等待10秒钟
  • 第二个事务在此期间开始,并且不能执行选择,因为它被第一个事务锁定
  • 第一个事务完成等待,更新表和提交
  • 然后,第二个事务可以继续并且不执行任何操作,因为状态为1的所有行都已更新

不幸的是,我看到的结果远非如此 - 事务处于死锁状态,其中一个被SQL Server杀死.我真的不明白为什么会这样,因为他们以相同的顺序访问资源.

以下是测试所需的脚本:

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
Run Code Online (Sandbox Code Playgroud)

补充说明:

  • 我只用表中的一行来测试它.
  • 将级别更改为可序列化也会导致死锁.
  • 这个代码看起来像这样的原因是因为我试图模拟ORM将要做什么 - 首先获取实体,然后检查代码是否为状态为1然后WHERE根据PK 发送更新.我知道我可以在没有ORM更新的情况下编写该代码WHERE ExecutionState_Status = 1

Ser*_*erg 2

这个假设是错误的:

第二个事务同时启动,并且无法执行选择,因为它被第一个事务锁定

两个repeatable read事务都select获取并保持S对密钥的锁定,直到commit。 S锁是兼容的。update当尝试获取X与锁不兼容的锁时,它们会陷入死锁S。与此相反,select在read commited事务中立即释放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 
Run Code Online (Sandbox Code Playgroud)