SQL Server 查询 - 为什么会出现死锁?

标签 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 与 20)和延迟(10 秒与 15 秒)。

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

但这不是我想要的 - 我只想执行一个,而第二个不应该执行任何操作。这就是为什么我尝试将级别更改为可重复读取。根据我的知识(我现在想挑战)它应该是这样的:

  • 第一个事务启动并锁定它读取的行
  • 第一笔交易将等待 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 时 checkin 代码,然后使用 WHERE 发送更新 基于PK。我知道我可以在没有 ORM 更新的情况下编写该代码 WHERE ExecutionState_Status = 1

最佳答案

这个假设是错误的:

second transaction starts in the meantime and cannot execute the select since it's locked by the first one

两个可重复读取事务的select都获取并保持S键上的锁,直到提交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 

关于SQL Server 查询 - 为什么会出现死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39286720/

相关文章:

MySQL 最长前缀匹配 100 万条记录与 3000 种可能性

php - 如何在mysql数据库中插入带有文件 uploader 值的多个文本框?

c# - 如何在 Dataset DataTable 中显示 DataColumns

mysql - 具有可重复读取隔离级别的 MySQL 中的更新插入

mysql - MySQL 和 PostgreSQL 中可重复读取语义的差异

sql - db设计评论家/建议

sql - 我怎样才能只选择不在另一种行的一周内的行?

sql-server - SQL Server 中的锁定序列

php - 使用新的 wp_query 将 sql 查询转换为 wordpress 查询

postgresql - 什么是 Postgres 中的默认事务行级别和/或 Sequelize.js 中默认使用的 LOCK