开头例行叠个甲。
我有个朋友,有套实时交易系统在一次变更后出现了比较严重的数据锁等待问题,超过了业务的最大忍耐时长,造成数百笔交易超时。在征得他的同意后,我把相关情况整理发在这里。
变更内容介绍
该实时交易系统使用的是DB2数据库,有一张在线交易表ACTJRNL数据量较大,长时间未清理。为保证处理效率,释放存储空间,系统管理员新建了一张表,把需要保留的数据导入进去,原表rename成ACTJRNL_BAK,新表上的索引均按照原表的索引新建。完成后,执行了reorg和runstats。
语句如下:
db2 "reorg table ACTJRNL"
db2 "runstats on table ACTJRNL with distribution and detailed indexes all"
笔者认为这里的REORG操作无需执行,当然,执行了也不会产生坏处。
问题情况介绍
变更后的第二天上午9-10点,正好是业务高峰期,系统管理员通过监控观察到有大量的锁等待。通过锁等待事件抓到语句如下:
UPDATE ACTJRNL SET ACTSTAT = 'PR**' ,GCDATE = '2025-04-27' ,GCRND = '****' WHERE REQDATE = '2025-04-27' AND BUSISEQNO = '***********************' AND SYSID = '****' AND PROCTYPE = '1'
相关字段值出于隐私考虑已使用*号替换,不影响阅读,请放心食用。
持有锁和等待锁的均为类似的语句,只是BUSISEQNO值有区别。
这条语句是业务的高频SQL,出现大量锁等待之后,执行时间立马变长了。该业务系统对时延是比较敏感的,当天出现了数百笔的交易超时。
问题处理经过
手工执行SELECT语句:
SELECT * FROM ACTJRNL WHERE REQDATE = '2025-04-27' AND BUSISEQNO = '***********************' AND SYSID = '****' AND PROCTYPE = '1'
执行返回很快,高峰期不到0.4s。
查看执行计划,发现走了索引,COST也很低,使用db2advis也基本没有更好的索引建议。
查看系统资源,CPU负载不高,内存使用无异常,磁盘繁忙度不超过20%,可见不是硬件资源瓶颈。
因为观察到个别缓冲池命中率不是太高,系统管理员将几个缓冲池、排序堆、编译内存堆等内存参数做了适当的增大,但问题依然存在。
元凶究竟是谁
回到问题现象来,变更后出现大量的锁等待,且为类似的SQL语句,可能是语句加锁的范围变大了,但该语句执行计划是走索引的,为什么?
我们再来仔细看下执行计划:

走的是Index:ACTJRNL_UNIQ_IDX2这个索引。
对比原来的执行计划(还好原表做了rename):

可以看到原来的执行计划走的是ACTJRNL_BUSISEQNO_IDX这个索引,cost低了很多。
查看该表表结构,一共有三个索引:
索引1 ACTJRNL_UNIQ_IDX2 字段如下
REQDATE, REQSEQNO, PROCTYPE, ACTTYPE
索引2 ACTJRNL_BUSISEQNO 字段如下
SYSID, PROCTYPE, MSGTYPE, GCDATE, GCRND, ACTSTAT
索引3 ACTJRNL_BUSISEQNO_IDX 字段如下
BUSISEQNO, SYSID,PROCTYPE
回到这条SQL语句的WHERE条件
WHERE REQDATE = '2025-04-27' AND BUSISEQNO = '***********************' AND SYSID = '****' AND PROCTYPE = '1'
实际通过WHERE条件的过滤结果只有1条记录。
如果只是用BUSISEQNO这一个条件过滤,结果为2条。加不加REQDATE或是其它字段,都不影响过滤结果。
而只是用REQDATE去查询,结果为十万-百万级别条记录。
新的执行计划使用索引1,只有一个REQDATE字段能被匹配上,过滤效果大概是几十分之一,而老的执行计划使用索引3,匹配的是BUSISEQNO字段,基本就是精准匹配那一两条了。
那么,问题原因就定位了,不合理的执行计划走了错误的索引导致加锁范围的扩大。
UPDATE语句会对扫描到的行加U锁,最终对符合条件的数据行升级为X锁。
解决办法
既然定位了原因,那么如何让DB2选择那个正确的索引。
我们知道,优化器是根据一些统计信息来选择访问路径的,它认为索引1最合理,可能是缺乏表中列值的分布以及相关性等信息。
那么,就来让它知道真实情况,执行下面这条语句:
db2 "runstats on table T_BUSI_ACTJRNL on all columns and columns ((REQDATE,REQSEQNO,PROCTYPE,ACTTYPE),(BUSISEQNO,SYSID,PROCTYPE)) with distribution and detailed indexes all"
执行完成后,再次查看执行计划,发现执行计划正确走到了索引3。
题外话
观察这张表的三个索引,都有些问题。
索引1
REQDATE, REQSEQNO, PROCTYPE, ACTTYPE
REQDATE作为索引的第一个字段,过滤性不好,第二个REQSEQNO可能更合适。后面两个字段都是状态类字段,没必要加。
索引2
SYSID, PROCTYPE, MSGTYPE, GCDATE, GCRND, ACTSTAT
看字段名称均类似一些码值或状态字段,过滤性非常差,这个索引建的意义感觉不大。
索引3
BUSISEQNO, SYSID,PROCTYPE
问题最小的索引。
第一个BUSISEQNO过滤性已经很好了,后面两个字段加不加对过滤结果没什么影响,也没必要。
以上均为笔者个人之见,如有错误之处,欢迎大家指正!




