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

时间:2016-09-02 07:31:31

标签: 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 

第二件几乎相同:

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 vs 20)和延迟(10s vs 15s)。

我在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

附加说明:

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

2 个答案:

答案 0 :(得分:2)

这个假设是错误的:

  

第二次交易同时开始,无法执行   选择,因为它被第一个锁定

两个repeatable read次交易'select获取并保持S锁定密钥直到commitS锁是兼容的。当update尝试获取与X锁定不兼容的S锁定时,它们会陷入僵局。 与此相反,select交易中的read commited即时发布S锁定。{/ p>

使用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 

答案 1 :(得分:1)

我得到link来回答这里发生的事情。这个例子几乎与我的相同,所以我不在这里复制它。

现在引用说明:

  

隔离级别可能发生第二种类型的死锁   如果您读取数据以更新数据,则可重复读取   后来。让我们看一下简单事务的T-SQL代码。

     

要导致此类死锁,您只需运行该事务即可   跨多个会话。您甚至不需要访问不同的数据   您可以从代码中看到范围。让我们试着解释一下会发生什么   这里。此事务跨多个会话运行时   同时,所有会话都可以获取共享锁以供阅读   数据。

     

因为您持有共享锁,直到交易结束   (COMMIT或ROLLBACK)在Repeatable Read中,以下更新   声明无法获取必要的更新锁,因为它们是   已被不同会话中获取的共享锁阻止。   死锁!

解决方案 - 将WITH (UPDLOCK)添加到第一个选择:

SELECT @jobName = JobName
FROM dbo.JobDetails  WITH (UPDLOCK)
WHERE ExecutionState_Status = 1

现在我需要考虑如何将该解决方案应用于ORM ..