暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

突发锁等待异常,一条命令解决!

烈焰枷锁 2025-04-27
98

开头例行叠个甲。

我有个朋友,有套实时交易系统在一次变更后出现了比较严重的数据锁等待问题,超过了业务的最大忍耐时长,造成数百笔交易超时。在征得他的同意后,我把相关情况整理发在这里。

变更内容介绍

该实时交易系统使用的是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过滤性已经很好了,后面两个字段加不加对过滤结果没什么影响,也没必要。


以上均为笔者个人之见,如有错误之处,欢迎大家指正!




文章转载自烈焰枷锁,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论