oracle在一个更新语句中死锁?

Tic*_*cks 3 oracle deadlock sql-update

首先是我的oracle版本:

SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE    11.2.0.4.0      Production
TNS for Linux: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production
Run Code Online (Sandbox Code Playgroud)

我创建表并插入两行:

create table test_table
(
    objectId VARCHAR2(40) not null,
    dependId VARCHAR2(40) not null
);

insert into test_table values(1, 10000);
insert into test_table values(2, 20000);
commit;
Run Code Online (Sandbox Code Playgroud)

然后打开两个会话,依次执行以下命令。

情况1:

会话1:

update test_table set dependId=100000 where objectid in (2);
Run Code Online (Sandbox Code Playgroud)

会话2:

update test_table set dependId=200000 where objectid in (1,2);
Run Code Online (Sandbox Code Playgroud)

见1:

update test_table set dependId=100000 where objectid in (1);
Run Code Online (Sandbox Code Playgroud)

和 session2 显示ORA-00060: deadlock detected while waiting for resource

案例2

会话1:

update test_table set dependId=100000 where objectid in (1);
Run Code Online (Sandbox Code Playgroud)

会话2:

update test_table set dependId=200000 where objectid in (2,1);
Run Code Online (Sandbox Code Playgroud)

见1:

update test_table set dependId=100000 where objectid in (2);
Run Code Online (Sandbox Code Playgroud)

并且不会发生死锁。

请解释原因。怎么update ... where objectid in (1,2)锁?

Chr*_*xon 5

这取决于数据库尝试获取行锁的顺序。

在您的示例中,objectid = 1 是表中的“第一个”。您可以通过按 rowid 对数据进行排序来验证这一点:

create table test_table
(
    objectId VARCHAR2(40) not null,
    dependId VARCHAR2(40) not null
);

insert into test_table values(1, 99);
insert into test_table values(2, 0);
commit;

select rowid, t.* from test_table t
order  by rowid;

ROWID                 OBJECTID    DEPENDID   
AAAT9kAAMAAAdMVAAA    1           99          
AAAT9kAAMAAAdMVAAB    2           0     
Run Code Online (Sandbox Code Playgroud)

如果在会话 1 中您现在运行:

update test_table set dependId=100000 where objectid in (2);
Run Code Online (Sandbox Code Playgroud)

您正在更新表中的“第二”行。当会话 2 运行时:

update test_table set dependId=200000 where objectid in (2,1);
Run Code Online (Sandbox Code Playgroud)

它读取数据块。然后尝试按照它们的存储顺序获取它们的锁。因此它查看第一行(objectid = 1),询问“是否已锁定?” 发现答案是否定的。并锁定该行。

然后对第二行重复此过程。它会话 1 锁定。查询时v$lock,您应该看到两个条目在 lmode = 6 中请求“TX”锁定。每个会话一个:

select sid from v$lock
where  type = 'TX'
and    lmode = 6;

SID   
    75 
    60 
Run Code Online (Sandbox Code Playgroud)

因此,在这一阶段,两个会话都锁定了一行。并且会话 2 正在等待会话 1。

在会话 1 中,您现在运行:

update test_table set dependId=100000 where objectid in (1);
Run Code Online (Sandbox Code Playgroud)

轰!僵局!

好的,但是我们如何确定这是由于行的存储顺序造成的呢?

使用属性聚类(12c 功能),我们可以更改行存储在块中的顺序,因此 objectid = 2 是“第一个”:

alter table test_table 
  add clustering 
  by linear order ( dependId );

alter table test_table move;

select rowid, t.* from test_table t
order  by rowid;

ROWID                 OBJECTID    DEPENDID   
AAAT9lAAMAAAdM7AAA    2           0           
AAAT9lAAMAAAdM7AAB    1           99   
Run Code Online (Sandbox Code Playgroud)

重复测试。在第 1 节中:

update test_table set dependId=100000 where objectid in (2);
Run Code Online (Sandbox Code Playgroud)

所以这已经锁定了“第一”行。在第 2 节中:

update test_table set dependId=200000 where objectid in (2,1);
Run Code Online (Sandbox Code Playgroud)

这试图锁定“第一”行。但不能,因为会话 1 已将其锁定。所以此时只有会话 1 持有任何锁。

检查v$lock以确保:

select sid from v$lock
where  type = 'TX'
and    lmode = 6;

SID   
    60 
Run Code Online (Sandbox Code Playgroud)

果然,当您在会话 1 中运行第二次更新时,它会完成:

update test_table set dependId=100000 where objectid in (1);
Run Code Online (Sandbox Code Playgroud)

笔记

这并不意味着update保证按照行在表块中存储的顺序锁定行。添加或删除索引可能会影响此行为。Oracle 数据库版本之间可能会发生变化。

关键点是必须按某种update顺序锁定行。它无法立即获取要更改的所有行的锁。

因此,如果您有两个或多个具有多个更新的会话,则可能会出现死锁。因此,您应该通过锁定要使用 更改的所有行来开始事务select ... for update