ITPub博客

首页 > 数据库 > Oracle > 一条"莫名其妙变慢"的统计SQL:从一个 Sequence 的 CACHE 说起

一条"莫名其妙变慢"的统计SQL:从一个 Sequence 的 CACHE 说起

原创 Oracle 作者:a960549548 时间:2026-06-18 10:12:43 0 删除 编辑

起因

前几天接到客户反馈,说绩效核算的查询 SQL 变慢了。原来 10 分钟就能出结果,这次跑了 13 分钟还在转。

按惯例先问环境,客户那边给的背景信息挺有意思:每个月 6 号 12 点开始,HIS 库会做一次 exp 全库导出。这个月因为赶上国庆,前几天没跑,今天这一轮到现在还没结束。

听到这,第一反应是——是不是 exp 导出占着资源把业务查询挤了?这个怀疑先放着,但没急着下结论,毕竟"赶时间"和"真变慢"是两回事,得拿数据说话。客户把那条慢 SQL 也发过来了。

定位 SQL

慢的 SQL 是一条带 sequence.nextval 的大 INSERT/SELECT,长这样:

sql
select hisfeemx_seq.nextval, js_rq, sfxm_id, ypcd_id, fyfl_id, sl, je,       sflx_id, sqys_id, sqrks_id, lx, plxs, sqr, sqks, sysdate, sfxm_dj ...

先用 HIS 用户跑了一下,确实慢。然后切到 SYS 用户,按 sql_text 把 sql_id 找出来:

sql
select sql_id  from v$sqlarea where sql_text like 'select hisfeemx_seq.nextval,js_rq,sfxm_id,ypcd_id,fyfl_id,sl,je,sflx_id,sqys_id,sqrks_id,lx,plxs,sqr,sqks,sysdate,sfxm_dj%';

拿到 sql_id 是 cxcspsvxpydc3。反查一下确认没认错:

sql
select sql_text from v$sqlarea where sql_id='cxcspsvxpydc3';

没问题,就是它。

看执行计划

SQL 变慢,最常规的怀疑就是执行计划变了——统计信息陈旧、新加了索引、绑定变量窥视之类。拉一下 AWR 里存的计划:

sql
select * from table(dbms_xplan.display_awr('cxcspsvxpydc3'));

结果让人有点意外:计划没动过,该走的索引都还在走,跟以前一模一样。那问题就不在优化器这边了。

复现 + 抓等待事件

光看计划没用,得让它慢给我看。建了张空表先压一压环境:

sql
create table sys.his_fee_detail_ylr as select * from his.his_fee_detail where 1=0;

把 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)的主意。

嫌疑人:CACHE_SIZE

直接去看这个 sequence 的配置:

sql
select * from dba_sequences where sequence_owner='HIS' and sequence_name=upper('hisfeemx_seq');

CACHE_SIZE 是 100。

这里有个细节值得展开: LAST_NUMBER 已经到了 1.59 亿。CACHE 100 意味着,每消耗 100 个值,实例就得回字典缓存里去取一次、更新一次序列对象。这条 INSERT 是逐行取 nextval 的,数据量又是百万级,等于这一亿多次取值被切成了上百万次的字典缓存刷新—— row cache lock 就是这么被挤出来的。RAC 环境下还会更夸张,因为序列缓存在实例间不共享。

跟客户确认后,把 CACHE 调到 1000:

sql
alter sequence HIS.HISFEEMX_SEQ cache 1000;

再看:

text
CACHE_SIZE  LAST_NUMBER

----------  -----------
1000        159414287

到这里,已经能初步判断:变慢就是 sequence 的 CACHE_SIZE 太小造成的。但 DBA 干活讲究个证据闭环,得自己再压一遍。

自己压一遍验证

为了排除"会不会是别的因素",干脆新建一个干净的 sequence,只动 CACHE 这一个变量:

sql
create sequence seq_tmp  increment by 1  start with 1  nomaxvalue  nocycle;

然后用两档 CACHE 各跑一次报表:

sql
alter sequence SEQ_TMP cache 1000;   
-- 跑 8~9 月报表,数据量 1,463,376,耗时 00:14:12.77
alter sequence SEQ_TMP cache 10;     
-- 跑 7~8 月报表,数据量 1,561,164,耗时 00:16:52.29

两次数据量在同一量级(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/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
Oracle ACE,MySQL 8.0 OCP,PMP,拥有OceanBase OBCP,TiDB PCTP,GoldenDB GDCP等多项认证,ITPUB认证专家,微信公众号:DBA之友。

注册时间:2022-02-08

  • 博文量
    132
  • 访问量
    171504