日期:2014-05-17  浏览次数:20840 次

ORACLE 的一个行锁问题
我用ASP写了一段代码,逻辑如下
1 select max(XXX) from tableA where ....
2 update tableA set XXX=max(XXX)+1 where ...
......资料处理逻辑......
3 调用 store Procedure
 store Procedure中会执行: select XXX from tableA where ... for update
(执行到这时,Oracle会报出错误讯息:ORA-00060: 等候資源時偵測到一個死結)
update tableA set XXX=max(XXX)+1 where ...

看到这可能有人会说,这种做法当然会报错了,
但我的问题是这段逻辑我在测试DB中执行并没有报错,直到部署到正式环境后才暴露出问题
所以请问高手们,这是否和DB的环境配置有关,还是什么其他的因素导致在一个环境中出错,而在另一个环境中没有问题

谢谢啦

------解决方案--------------------
select XXX from tableA where ... for update
这条语句锁定的数据过多。
当多用户并发时同时执行同样这一条语句,如果tableA数据量大,
且where后条件不能很好的利用索引,会导致各自锁定一部分数据,
互相等待对方锁定的那部分数据行的锁释放,这样导致死锁的可能性大大
增加。

你的测试环境没有出现的原因,可能是数据量较小。

解决办法是,可以将最大值先存入另一张表,只有一行记录。
然后使用select * from update主动锁定然后+1更新、提交,这样就不
会造成死锁了。
------解决方案--------------------
update tableA set XXX=max(XXX)+1 where ...
我觉得这个做法问题比较大,他会为max()要求锁住表中的所有行不让修改,直到事务提交。
假设这个时候同时有两个客户端。
a会话先select select XXX from tableA where ...for update已经锁住第6行
b会话进来也使用select select XXX from tableA where ...for update锁住第7行
a会话执行update tableA set XXX=max(XXX)+1 where ...把要求全表锁。但是被b锁住了第7行,所以a等待
b会话执行update tableA set XXX=max(XXX)+1 where ...把要求全表锁,但是被a锁住了第6行,所以也等待。
此时死锁就出现了。

但是lz说确保只有一个会话,那是不可能的,一个会话绝无可能产生死锁毫无疑问的.
------解决方案--------------------
首先,因为oracle的锁实现机制的原因,无法从现有任何一个表或视图知道锁住了具体的哪一行。唯一的办法是你用select ...for update not wait来测试。
所以得想其他办法来观察,
这里提供一段tom的sql,用量查看谁阻塞了谁:
select 
(select username from v$session where sid = a.sid) bloker,
a.sid,' is bloking ',
(select username from v$session where sid=b.sid) blockee,
b.sid
from v$lock a ,v$lock b
where a.block = 1 and b.request > 0 and a.id1 = b.id1 and a.id2=b.id2;

还有一断用来查看锁和相关对象的信息
select /*+ RULE */ ls.osuser os_user_name, ls.username user_name,
decode(ls.type, 'RW', 'Row wait enqueue lock', 'TM', 'DML enqueue lock', 'TX', 
'Transaction enqueue lock', 'UL', 'User supplied lock') lock_type,
o.object_name object, decode(ls.lmode, 1, null, 2, 'Row Share', 3, 
'Row Exclusive', 4, 'Share', 5, 'Share Row Exclusive', 6, 'Exclusive', null) 
lock_mode, o.owner, ls.sid, ls.serial# serial_num, ls.id1, ls.id2
from sys.dba_objects o, ( select s.osuser, s.username, l.type,
l.lmode, s.sid, s.serial#, l.id1, l.id2 from v$session s,
v$lock l where s.sid = l.sid ) ls where o.object_id = ls.id1 and o.owner 
<> 'SYS' order by o.owner, o.object_name
------解决方案--------------------
update tableA set XXX=max(XXX)+1 where ...
XXX字段是主键吗?如果是可能有问题,还有就是tableA有没有子表呢?