一个更新语句中的 oracle 死锁?
oracle deadlock in one update statement?
首先是我的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
我创建 table 并插入两行:
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;
然后打开两个session,依次执行以下命令
案例一:
会话 1:
update test_table set dependId=100000 where objectid in (2);
会话 2:
update test_table set dependId=200000 where objectid in (1,2);
第 1 期:
update test_table set dependId=100000 where objectid in (1);
并且会话 2 显示ORA-00060: deadlock detected while waiting for resource
案例2
会话 1:
update test_table set dependId=100000 where objectid in (1);
会话 2:
update test_table set dependId=200000 where objectid in (2,1);
第 1 期:
update test_table set dependId=100000 where objectid in (2);
并且没有发生死锁。
请说明原因。 update ... where objectid in (1,2)
如何持有锁?
这归结为数据库尝试获取行锁的顺序。
在你的例子中objectid = 1是"first"在table。您可以通过按 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
如果你现在在会话 1 运行:
update test_table set dependId=100000 where objectid in (2);
您正在更新 table 中的 "second" 行。当会话 2 运行s:
update test_table set dependId=200000 where objectid in (2,1);
读取数据块。然后尝试按照它们的存储顺序获取它们的锁。所以它查看第一行(objectid = 1),询问 "is the locked?" 发现答案是否定的。并锁定该行。
然后它对第二行重复这个过程。其中 被 会话 1 锁定。查询 v$lock
时,您应该看到两个条目请求 'TX' 在 lmode = 6 中锁定。每个会话一个:
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);
所以这已经锁定了 "first" 行。在会话 2 中:
update test_table set dependId=200000 where objectid in (2,1);
这会尝试锁定 "first" 行。但不能,因为会话 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
保证按行在 table 块中的存储顺序锁定行。添加或删除索引可能会影响此行为。 Oracle 数据库版本之间可能会发生变化。
关键点是update
必须按一些顺序锁定行。它无法立即获取所有将要更改的行的锁。
因此,如果您有两个或多个具有多个更新的会话,则可能会出现死锁。因此,您应该通过使用 select ... for 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
我创建 table 并插入两行:
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;
然后打开两个session,依次执行以下命令
案例一:
会话 1:
update test_table set dependId=100000 where objectid in (2);
会话 2:
update test_table set dependId=200000 where objectid in (1,2);
第 1 期:
update test_table set dependId=100000 where objectid in (1);
并且会话 2 显示ORA-00060: deadlock detected while waiting for resource
案例2
会话 1:
update test_table set dependId=100000 where objectid in (1);
会话 2:
update test_table set dependId=200000 where objectid in (2,1);
第 1 期:
update test_table set dependId=100000 where objectid in (2);
并且没有发生死锁。
请说明原因。 update ... where objectid in (1,2)
如何持有锁?
这归结为数据库尝试获取行锁的顺序。
在你的例子中objectid = 1是"first"在table。您可以通过按 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
如果你现在在会话 1 运行:
update test_table set dependId=100000 where objectid in (2);
您正在更新 table 中的 "second" 行。当会话 2 运行s:
update test_table set dependId=200000 where objectid in (2,1);
读取数据块。然后尝试按照它们的存储顺序获取它们的锁。所以它查看第一行(objectid = 1),询问 "is the locked?" 发现答案是否定的。并锁定该行。
然后它对第二行重复这个过程。其中 被 会话 1 锁定。查询 v$lock
时,您应该看到两个条目请求 'TX' 在 lmode = 6 中锁定。每个会话一个:
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);
所以这已经锁定了 "first" 行。在会话 2 中:
update test_table set dependId=200000 where objectid in (2,1);
这会尝试锁定 "first" 行。但不能,因为会话 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
保证按行在 table 块中的存储顺序锁定行。添加或删除索引可能会影响此行为。 Oracle 数据库版本之间可能会发生变化。
关键点是update
必须按一些顺序锁定行。它无法立即获取所有将要更改的行的锁。
因此,如果您有两个或多个具有多个更新的会话,则可能会出现死锁。因此,您应该通过使用 select ... for update
.