案例背景
环境:功能测试环境,Linux + WAS 6.1 + DB2 9.5
问题描述:功能测试组反馈某APP上报业务一直失败,无法成功。
分析过程
查看后台日志,出现如下错误,调用cm的ICmOmDivisionServiceEx的setStationDivision方法时,调用失败,总共调用了3次。
查询数据库锁等待情况,发现CM_PM_FRM_FUND表存在锁等待,无限时的锁等待(未设置锁等待超时)。。。
此时,跟CM_PM_FRM_FUND表有关的查询模块一直处于转圈圈的状态,这是因为数据被X锁了,其他想要查询的会话均处于NS锁等待状态。
既然有锁等待,那么先查询X锁了种植主体的会话正在执行的SQL,命令:
db2 get snapshot for applications on TSO >tsoapp.txt
如果是DB2 v9.7以上版本,可以通过db2top监控。
从tsoapp.txt中搜索导致会话堵塞的会话 Application handle = 21952
查看该会话正在执行的SQL(Dynamic SQL statement text:):
select s.frm_cd,
m.division_unique_cd,
m.certificate_no,
m.certificate_type_ek,
m.psn_name ,
m.frm_member_info_id,
m.frm_fund_unique_id,
m.member_unique_id,
m.hf_relation_ek,
m.if_sell_qualfy ,
m.share_rate,
m.plow_area,
m.share_amt,
m.appoint_area,
m.appoint_wght,
m.famy_psn_qty,
m.work_psn_qty ,
m.out_work_psn_qty,
m.send_state,
m.modify_time,
m.data_state,
m.biz_year,
m.photo_id,
m.sex_ek,
m.MEN_TECH_GRADE_EK,
m.MEN_EDUCATION_EK,
m.MEN_MOBILE
from cm_pm_frm_member_info m
left join cm_pm_frm_fund s
on (m.member_unique_id = s.frm_fund_unique_id and s.biz_year = 2017 and
s.data_state = '1')
where m.data_state = '1'
and m.frm_fund_unique_id = 'a44df2d5857b4657bf316150d37e5bef'
and m.biz_year = 2017
该SQL的执行速度并不慢,在DB2客户端下执行在1秒以内就返回了。从数据库会话状态来看,它并没有处于被堵塞的状态。
再从tsoapp.txt中搜索被堵塞会话正在执行的SQL,发现它们都正在执行下面这句SQL:
insert into cm_om_div_org_rela (div_org_rela_id, org_unique_cd, division_unique_cd, begin_year, end_year, send_state, modify_time, data_state, biz_type_ek) (select sys_guid(), m.station_unique_cd, m.division_unique_cd, 2017, 9999, '0', '2017-05-08 09:31:14', '1', 'CM' from view_cm_om_frm_fund m where m.biz_year = 2017 and m.station_unique_cd = '11520306000000000003' and m.division_unique_cd not in (select s.division_unique_cd from cm_om_div_org_rela s where s.BIZ_TYPE_EK = 'CM' and s.org_unique_cd = '11520306000000000003' and s.data_state = '1' and s.begin_year <= 2017 and s.end_year > 2017))
该SQL在上报过程中执行,确实会处于锁等待状态。而释放掉锁后再去执行,速度也很快。
分析锁数据,发现是21952这个会话X锁了CM_PM_FRM_FUND表数据,而其它会话需要对这些数据进行NS查询,因此进入锁等待状态。但是我们当前请求的只有一个上报操作,为什么会产生这样的锁等待呢?
跟踪线程快照,卡住的代码位置与WAS日志中报错信息的位置一致,都是pm的setStationDivision调用cm的setStationDivision时,出现了调用失败。
那么,会不会是这种情况呢?
上报数据的时候,PM先把某条CM_PM_FRM_FUND的记录给X锁(Insert或者Update)了未提交,然后在同一个事务里面,又调用CM的setStationDivision接口,而该接口执行insert into cm_om_div_org_rela这个语句时,会对CM_PM_FRM_FUND表进行一次汇总查询需要加NS锁,于是这个语句就进入了锁等待状态,CM的setStationDivision接口在等待PM释放之前X锁上的那条CM_PM_FRM_FUND记录,而PM在等待CM接口的返回结果,然后就一直死等下去了,这是另外一种形式的死锁啊。
解决问题
开发优化程序代码,不在PM中调用CM的setStationDivision接口。
案例小结
这里涉及到几个知识点:
- dubbo服务调用时,默认是会重试三次的,这个要小心(比如Insert 的业务如果重试三次会有什么后果)
- DB2默认隔离级别下,查询是要上NS共享查询锁的,NS锁与X锁是不能兼容的;
- DB2的锁等待可以设置超时时间,如果没设置,默认是无限期等待下去;