甲骨文死锁在一条更新语句?

时间:2019-06-27 11:25:31

标签: oracle sql-update deadlock

第一个我的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

我创建表并插入两行:

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;

然后打开两个会话并按顺序执行以下命令。

案例1:

会话1:

update test_table set dependId=100000 where objectid in (2);

session2:

update test_table set dependId=200000 where objectid in (1,2);

seesion1:

update test_table set dependId=100000 where objectid in (1);

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

案例2

会话1:

update test_table set dependId=100000 where objectid in (1);

session2:

update test_table set dependId=200000 where objectid in (2,1);

seesion1:

update test_table set dependId=100000 where objectid in (2);

并且没有死锁发生。

请说明原因。 update ... where objectid in (1,2)如何保持锁定状态?

1 个答案:

答案 0 :(得分:2)

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

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

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     

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

update test_table set dependId=100000 where objectid in (2);

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

update test_table set dependId=200000 where objectid in (2,1);

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

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

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

SID   
    75 
    60 

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

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

update test_table set dependId=100000 where objectid in (1);

嘘!死锁!

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

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

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   

重复测试。在会话1中:

update test_table set dependId=100000 where objectid in (2);

因此,这已锁定“第一”行。在会话2中:

update test_table set dependId=200000 where objectid in (2,1);

这将尝试锁定“第一”行。但是不能,因为会话1已将其锁定。因此,目前只有会话1拥有任何锁。

检查v$lock以确保:

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

SID   
    60 

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

update test_table set dependId=100000 where objectid in (1);

注意

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

关键是update必须按 some 顺序锁定行。它不能立即获取将要更改的所有行的锁。

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