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 中并行执行它们 - 两个选项卡。现在的问题 - 读取提交的事务隔离级别按预期工作 - 最后修改被应用并且两个脚本都成功执行。

但这不是我想要的 - 我只想执行一个,而第二个什么也不做。这就是为什么我试图将级别更改为可重复读取。据我所知(我现在想挑战一下)它应该是这样的:

不幸的是,我看到的结果远非如此 - 交易陷入僵局,其中一个交易被 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

补充说明:

我得到了一个 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 密钥锁,直到 commitS 锁兼容。当 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