前几天接到客户反馈,说绩效核算的查询 SQL 变慢了。原来 10 分钟就能出结果,这次跑了 13 分钟还在转。
按惯例先问环境,客户那边给的背景信息挺有意思:每个月 6 号 12 点开始,HIS 库会做一次
exp 全库导出。这个月因为赶上国庆,前几天没跑,今天这一轮到现在还没结束。
听到这,第一反应是——是不是 exp 导出占着资源把业务查询挤了?这个怀疑先放着,但没急着下结论,毕竟"赶时间"和"真变慢"是两回事,得拿数据说话。客户把那条慢 SQL 也发过来了。
慢的 SQL 是一条带
sequence.nextval 的大 INSERT/SELECT,长这样:
先用 HIS 用户跑了一下,确实慢。然后切到 SYS 用户,按
sql_text 把
sql_id 找出来:
拿到
sql_id 是
cxcspsvxpydc3。反查一下确认没认错:
没问题,就是它。
SQL 变慢,最常规的怀疑就是执行计划变了——统计信息陈旧、新加了索引、绑定变量窥视之类。拉一下 AWR 里存的计划:
结果让人有点意外:计划没动过,该走的索引都还在走,跟以前一模一样。那问题就不在优化器这边了。
光看计划没用,得让它慢给我看。建了张空表先压一压环境:
把
hisfeemx_seq.nextval 临时置成 1,重跑这条 INSERT/SELECT——10 分钟出结果,跟客户说的"原来"差不多。说明 SQL 本身没问题,环境也没崩。
关键的一步是顺手看了等待事件:
EVENT COUNT(*)
------------------------------ ----------
db file sequential read 1
row cache lock 1
db file scattered read 1
cursor: pin S ...
row cache lock 出现了。这个等待事件一出来,方向基本就锁定了——它常见于字典缓存的争用,而
取 sequence 的下一个值,本质上就是在打 row cache(DC_SEQUENCES)的主意。
直接去看这个 sequence 的配置:
CACHE_SIZE 是 100。
这里有个细节值得展开:
LAST_NUMBER 已经到了 1.59 亿。CACHE 100 意味着,每消耗 100 个值,实例就得回字典缓存里去取一次、更新一次序列对象。这条 INSERT 是逐行取
nextval 的,数据量又是百万级,等于这一亿多次取值被切成了上百万次的字典缓存刷新——
row cache lock 就是这么被挤出来的。RAC 环境下还会更夸张,因为序列缓存在实例间不共享。
跟客户确认后,把 CACHE 调到 1000:
再看:
为了排除"会不会是别的因素",干脆新建一个干净的 sequence,只动 CACHE 这一个变量:
然后用两档 CACHE 各跑一次报表:
两次数据量在同一量级(150 万上下),CACHE 从 1000 降到 10,时间从 14 分钟出头涨到近 17 分钟—— 同比慢了 10% 左右。
这条曲线和客户的体感是吻合的:原本 10 分钟,变成 13 分钟还没出,差不多也是 10%~30% 的劣化区间。
回过头看这次排查,有几个点值得记下来:
1. 别被表象带跑。 exp 全库导出在跑、赶上节假日补跑——这些线索很诱人,但执行计划没变、测试环境复现正常这两步,就已经把"资源被抢占"和"统计信息变了"这两条岔路堵死了。如果一开始就钻进 exp 的方向,可能查一天都查不出来。
2. 等待事件永远是最短路径。
row cache lock 这一行直接把范围从"SQL 层面"收窄到了"字典缓存层面",sequence 才进入视野。
3. 生产库的 sequence 别默认用小 CACHE。 Oracle 默认 CACHE 20,很多人建序列时图省事不指定,或者随手填个 100。对于像这种主键/流水号、单次大批量 INSERT 取值的场景,1000 是个比较稳的下限;千万级以上、特别是 RAC,给到 10000 也不为过。代价只是实例异常宕机时丢掉一部分未提交的缓存值——对绝大多数业务流水号来说,这是可以接受的。
4. 序列号已经很大时,小 CACHE 的杀伤是放大的。 这次
LAST_NUMBER 都到 1.5 亿了,每 100 个值一次字典刷新,频率高得离谱。新业务感觉不出来,等量级上来了,某天突然"莫名其妙变慢"——很可能就是它。
最后,
alter sequence ... cache 是个
在线、即时生效的改动,不用停业务、不用重启,调完立竿见影。这次处理完,客户那边的绩效核算就恢复了原来的速度。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3174002/ ,如需转载,请注明出处,否则将追究法律责任。