使用不同索引导致死锁

问题描述 投票:0回答:1

以下测试用例会在 SQL Server 中导致死锁,但不会在 Oracle 中导致死锁。

流程 1 选择主键列一条具有精确命中 where 子句的记录。使用一个索引。 进程 2 使用行级锁定和另一个索引以类似于 fifo 的方式从表中选择相同的记录,并按预期阻塞。 流程1通过主键删除记录。

对于 SQL Server,这会导致死锁。 使用 Oracle 不会发生死锁。

有没有办法避免 SQL Server 中的死锁?

您将在下面找到有关如何重现和比较该行为的更多详细信息。

SQL 服务器

SQL Server 版本 2019 和 Windows 10

设置

create table deadlocktest (
  pk int, 
  id1 int, 
  id2 int, 
  x int, 
  y int, 
  seq int, 
  primary key nonclustered  (pk) );

create UNIQUE NONCLUSTERED index  idx_id1 on deadlocktest(id1, x, seq);
create UNIQUE NONCLUSTERED index  idx_id2 on deadlocktest(id2);
create UNIQUE NONCLUSTERED index  idx_seq on deadlocktest(seq);
  
insert into deadlocktest values (1, 10, 100, 1000, 10000, 1);
insert into deadlocktest values (2, 20, 200, 2000, 20000, 2);

选项 READ_COMMITTED_SNAPSHOT 和 ALLOW_SNAPSHOT_ISOLATION 已启用。

测试用例

自动提交已关闭,事务隔离级别 = READCOMMITTED。

T1: BEGIN TRANSACTION;
T2: BEGIN TRANSACTION;
T1: select top 1 pk, x from deadlocktest WITH (UPDLOCK ROWLOCK) where id1 = 10 and x = 1000 and seq = 1;
T2: select top 1 pk, x from deadlocktest WITH (UPDLOCK ROWLOCK) where id1 = 10 and x = 1000  order by seq;
    BLOCKED
T1: delete from deadlocktest where pk = 1;
T2: DEADLOCK ERROR

如果T1中的select不使用UPDLOCK ROWLOCK,也会发生同样的情况。它仅在此测试用例中用于控制执行顺序。

死锁图为: DeadlockGraph

左侧(受害者)是T2 select语句,右侧是T1 insert语句。

查询计划是(您将看到正在使用不同的索引):

select top 1 pk, x from deadlocktest WITH (UPDLOCK ROWLOCK) where id1 = 10 and x = 1000  order by seq
  |--Top(TOP EXPRESSION:((1)))
       |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))
            |--Index Seek(OBJECT:([XGEN].[dbo].[deadlocktest].[idx_id1]), SEEK:([XGEN].[dbo].[deadlocktest].[id1]=(10) AND [XGEN].[dbo].[deadlocktest].[x]=(1000)) ORDERED FORWARD)
            |--RID Lookup(OBJECT:([XGEN].[dbo].[deadlocktest]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)
select top 1 pk, x from deadlocktest WITH (UPDLOCK ROWLOCK) where id1 = 10 and x = 1000 and seq = 1
  |--Top(TOP EXPRESSION:((1)))
       |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))
            |--Index Seek(OBJECT:([XGEN].[dbo].[deadlocktest].[idx_seq]), SEEK:([XGEN].[dbo].[deadlocktest].[seq]=(1)) ORDERED FORWARD)
            |--RID Lookup(OBJECT:([XGEN].[dbo].[deadlocktest]), SEEK:([Bmk1000]=[Bmk1000]),  WHERE:([XGEN].[dbo].[deadlocktest].[id1]=(10) AND [XGEN].[dbo].[deadlocktest].[x]=(1000)) LOOKUP ORDERED FORWARD)
delete from deadlocktest where pk = 1
  |--Table Delete(OBJECT:([XGEN].[dbo].[deadlocktest]), OBJECT:([XGEN].[dbo].[deadlocktest].[PK__deadlock__321403CE902EA0FE]), OBJECT:([XGEN].[dbo].[deadlocktest].[idx_id1]), OBJECT:([XGEN].[dbo].[deadlocktest].[idx_id2]), OBJECT:([XGEN].[dbo].[deadlocktest].[idx_seq]))
       |--Index Seek(OBJECT:([XGEN].[dbo].[deadlocktest].[PK__deadlock__321403CE902EA0FE]), SEEK:([XGEN].[dbo].[deadlocktest].[pk]=CONVERT_IMPLICIT(int,[@1],0)) ORDERED FORWARD)

甲骨文

Windows 10 上的 Oracle XE 18

设置

create table deadlocktest (
  pk int, 
  id1 int, 
  id2 int, 
  x int, 
  y int, 
  seq int, 
  primary key (pk) );
  
insert into deadlocktest values (1, 10, 100, 1000, 10000, 1);
insert into deadlocktest values (2, 20, 200, 2000, 20000, 2);

create UNIQUE index  idx_id1 on deadlocktest(id1, x, seq);
create UNIQUE index  idx_id2 on deadlocktest(id2);
create UNIQUE index  idx_seq on deadlocktest(seq);

测试用例

自动提交已关闭,事务隔离级别 = READCOMMITTED。

T1: SET TRANSACTION READ WRITE;
T2: SET TRANSACTION READ WRITE;
T1: select pk, x  from deadlocktest where id1 = 10 and x = 1000 and seq = 1 for update;
T2: select pk, x from deadlocktest where id1 = 10 and x = 1000  order by seq for update;
    BLOCKED
T1: delete from deadlocktest where pk = 1;

无死锁,T1完成删除语句,T2一直阻塞直到T1结束。

附加信息

T1 中带锁的 select 用于此测试场景,以控制并发事务。在实际应用程序上下文中,此选择丢失,并且死锁如上面的死锁图中所示。

sql-server locking deadlock
1个回答
0
投票

这种僵局取决于计划。 如果优化器选择该计划,Oracle 上也可能发生这种情况。 所以这是您代码中的错误。

正如@Martin Smith所说

“对于T1,您可以使用WITH (UPDLOCK ROWLOCK, INDEX = idx_id1 )来确保它使用与T2相同的访问路径。否则两者都可以获得不同索引中的键的U锁。”

至于“在T1的选择中添加WITH(...,INDEX = idx_id1)似乎有效。但这也会影响优化器”

当然可以。 这就是重点。 您引入了一种表设计,如果优化器选择“错误”索引,该设计将导致死锁。 优化器不会以避免死锁的方式选择索引,除非您指示它这样做。

© www.soinside.com 2019 - 2024. All rights reserved.