客户的一台oracle 19c rac数据库,其中的一个实例出先hang死的情况,严重影响业务访问使用!应用人员当时已无法操作数据库,尝试进行杀死死锁的session,但未起到效果。
接受问题处理第一步还是先观察后台alert日志,发现有ora-00060错误,并伴随着十几秒很快频率的redo切换,并生成了大量trc文件,数据库redo大小是2g。
根据开发人员的描述是他们修改了一个存储过程,运行存储过程导致,但现在数据库hang死业务人员无法编译存储过程。
所以思路为:优先杀死相应session,再查看trc文件定位死锁位置!
但杀死session后死锁还是存在,os层杀死对应pid后数据库可以操作!
oracle上的死锁一般出现于“行级锁”的环形依赖情况下:
有记录a、b,事务t1、t2,现在t1、t2并发执行update(或delete)a b
事务t1操作的顺序为a-b,正常情况下会先后锁住a和b
事务t2操作的顺序为b-a,正常情况下会先后锁住b和a
t1、t2并发执行,t1锁住a 同时 t2锁住b;
t1操作完a想要锁住b,但b已被t2锁住,t1等待中;t2操作完b想要锁住a,但a已被t1锁住,t2等待中;
由于t1、t2都在等待又都不释放,因此造成死锁。
当死锁越来越多的时候,数据库连接被耗尽,不再能接受新的连接;数据库服务器的cpu和内存也可能会不够用了……程序猿就悲剧了。
解决办法:
在事务开始时或开始前先对操作数据按统一规则排序。
例如:按a-b顺序排序,使得t1、t2操作的顺序都为a-b,假设t1、t2并发执行时t1锁住了a;则t2只能等待a的锁了,b就没有机会被t2锁住了;这样t1就可以顺利锁住b,并顺利执行;当t1执行完成,释放a、b,t2就可以继续执行了。
从线上死锁的情况来看,主要发生在以下两种场景:
① 在同一个事务中无序的update或delete多条记录,事务并发执行可能会有死锁。
② 没有事务,但执行单条update或delete sql时,如果update或delete所影响的记录数大于1,也有可能出现死锁。
对于第①种场景可以使用前述的解决办法处理,即先排序后执行;
对于第②种场景的解决办法如下:
先用select查询出需要操作的记录主键,按主键排序,再通过主键一条一条迭代执行。
不过将一条语句变成多条语句执行会破坏原有的事务(jdbc默认事务:一条sql语句即一个事务),无法保证操作的原子性了,这就需要将这些操作放入一个事务中。或者可以使用 in 语句将其改造成一条sql,例如:update … where id in ( 1,2,3,4… ) …,这样效率会比多条sql语句高一些,而且 in 是可以命中索引的。
死锁查询:
select s.username,l.object_id, l.session_id,s.serial#, s.lockwait,s.status,s.machine,s.program from v$,v$locked_object l where s.sid = l.session_id;
username:死锁语句所用的数据库用户;
sid: session identifier, session 标示符,session 是通信双方从开始通信到通信结束期间的一个上下文。
serial#: sid 会重用,但是同一个sid被重用时,serial#会增加,不会重复。
lockwait:可以通过这个字段查询出当前正在等待的锁的相关信息。
status:用来判断session状态。active:正执行sql语句。inactive:等待操作。killed:被标注为删除。
machine: 死锁语句所在的机器。
program: 产生死锁的语句主要来自哪个应用程序。
查看引起死锁的语句:
select sql_text from v$sql where hash_value in (select sql_hash_value from v$session where sid in (select session_id from v$locked_object));
kill掉死锁:
通过死锁查询得到对应sid、serial#,使用有权限的用户执行:
alter system kill session ‘sid, serial#’;
如果未能成功释放死锁该如何处理:
kill掉死锁后我们再查询下死锁:
select s.username,l.object_id, l.session_id,s.serial#, s.lockwait,s.status,s.machine,s.program from ,v$locked_object l where s.sid = l.session_id;
那么有可能死锁的status状态变为了killed,但是此状态是无法释放资源的,那就需要通过sid去找到对应的pid进行系统层面的kill。
查vkatex parse error: expected 'eof', got '#' at position 29: …lect sid,serial#̲,paddr from v$session where sid=‘刚才查到的sid’
查v$process视图:
select spid from v$process where addr=‘刚才查到的paddr’;
kill -9 刚才查出的spid