羑悻的小杀马特.头像
关注
【金仓数据库征文】别急着加内存:一次 Oracle 迁国产库的实战复盘,慢 SQL 背后全是坑封面图

【金仓数据库征文】别急着加内存:一次 Oracle 迁国产库的实战复盘,慢 SQL 背后全是坑

那天早上九点,电话就开始响。省级平台刚切到新库,首页那张综合报表转了十几秒,点查询直接卡死,前端弹了一堆“请求失败”。业务部门没明说,但那意思我听得懂:Oracle 上跑得好好的,换完国产库怎么成这样了。

其实脚本、存储过程、视图、触发器,我们跟了大半年,基本都找到了对应写法,单元测试也跑过,心里原本是有底的。真到上线高峰,才发现“跑得动”和“跑得快”完全是两码事。

那几天我没敢乱动参数。组里有人一上来就想加内存、拉满 shared_buffers,我拦住了。太多“数据库不行”的锅,最后查下来都是 SQL 没写好、统计信息没更新,或者连接池太小。我们给自己定了个顺序:先连库看现场,再抓热点,再看计划,最后才动手。这话好说,真到业务催命的时候,能不能按住手才是关键。

我把这套流程随手画在纸上,贴在显示器边上,每次想跳步就抬头看一眼:

连库这步看着不起眼。图形客户端点过去,延迟 3ms,四百多张表一口气刷出来,起码说明网络、权限、字符集这些地基没问题。真要连库都磕磕绊绊,后面查出来的“慢”很可能是假象。

进了 ksql,第一句我就按总耗时排序,把最吃资源的 SQL 揪出来:

SELECT queryid, calls,
       round(total_exec_time::numeric/1000, 2)  AS sec,
       round(mean_exec_time::numeric/1000, 3)   AS avg_sec
  FROM sys_stat_statements
 ORDER BY total_exec_time DESC
 LIMIT 5;

结果很直观,一条统计报表 SQL 独占 126 秒,平均 8.9 秒。别的语句在它面前基本可以忽略:

我又按物理读排了一遍,怕漏掉那种单次不慢、但 I/O 积少成多的语句。sys_stat_statements 这个扩展我们上线前就配好了,不然这些数据根本抓不到。顺便提一句,要是你查出来是空,先确认扩展建了,track_activity_query_size 也够大,不然 SQL 文本会被截断,查了也白查。

光看耗时不够,得看它“怎么跑的”。给那条 SQL 加 EXPLAIN ANALYZE,问题直接摆在桌面上:t_apply_info 是 Seq Scan,近百万行硬扫,Filter 掉 88 万行。Rows Removed by Filter 是 882310,这个数字我记得特别清楚——数据库费老大劲读了一百万行,最后只留八万多。这种场景,不建索引,光靠堆内存是没用的。

动手的时候,我先建了个部分索引,CONCURRENTLY,避免锁表。业务只查 status=1 的待办,status 为别的行没必要进索引,索引体积能小一大截。建完顺手 ANALYZE,确认统计信息真的更新了。

CREATE INDEX CONCURRENTLY idx_apply_ctime
  ON t_apply_info (create_time)
  WHERE status = 1;

ANALYZE VERBOSE t_apply_info;

但建完索引也不是万事大吉。金仓的优化器和 Oracle 脾气不太一样,对成本估算偏保守。有条复杂报表 SQL,怎么都不肯走新索引,还是 Seq Scan。我没硬改业务逻辑,而是用 Hint 强行引导了一下:

/*+ IndexScan(a idx_apply_ctime) */
SELECT a.apply_no, a.create_time, u.user_name
  FROM t_apply_info a
  JOIN t_user u ON a.user_id = u.id
 WHERE a.create_time >= '2026-08-01'
   AND a.status = 1
 ORDER BY a.create_time DESC
 LIMIT 50;

Hint 这东西,我原则是能不用就不用。它相当于告诉优化器“别猜了,听我的”。万一数据分布变了,Hint 反而帮倒忙。这次算是临时止痛药,后面统计信息稳了,又回来验证了好几遍。

建完索引,我顺手查了下表膨胀情况。PostgreSQL 系里,死元组一多,再大的索引也救不回来:

SELECT relname, n_live_tup, n_dead_tup,
       last_vacuum, last_autovacuum, last_analyze
  FROM sys_stat_user_tables
 WHERE relname = 't_apply_info';

n_dead_tup 比例不高,last_analyze 是刚更新的时间戳,心里才踏实一点。

SQL 层面理顺后,高峰期还是偶尔抖。那台机器 64GB 内存,数据库独占。组里有人主张 shared_buffers 直接拉满 32GB,我以前在别的 PostgreSQL 系库上栽过这个坑——缓存太大,OS 缓存被挤占,反而开始换页,性能更差。最后按经验值调成这样:

shared_buffers        = 16GB
effective_cache_size  = 48GB
work_mem             = 64MB
maintenance_work_mem = 2GB
checkpoint_completion_target = 0.9

改完 reload,缓冲池命中率稳定在 92% 以上。顺手查了一眼:

SELECT round(sum(blks_hit) * 100.0
       / nullif(sum(blks_hit) + sum(blks_read), 0), 2) AS hit_ratio
  FROM sys_stat_database;

92.4%,不算激进,也不算保守,刚好。

白天救火告一段落,又冒出一种怪现象:页面点一下,等四五秒才有反应,但热点 SQL 明明不慢了。我第一反应是锁。查了一下,果然有会话在等长事务释放锁:

SELECT blocked.pid     AS blocked_pid,
       blocking.pid    AS blocking_pid,
       blocked.locktype,
       blocked.relation::regclass AS relation,
       blocked.wait_event_type || ':' || blocked.wait_event AS wait_event
  FROM sys_locks blocked
  JOIN sys_locks blocking
    ON blocking.locktype = blocked.locktype
   AND blocking.pid <> blocked.pid
 WHERE NOT blocked.granted;

定位到是应用侧一个没提交的长事务。联系开发改成“用完即提交”,排队现象当场消失。这种问题,不查锁根本发现不了,纯看慢 SQL 会走偏。

晚上打了份 KWR 报告,把高峰段采样出来复盘。TOP 5 SQL 里,前面那条统计报表和 UPDATE 已经被我们压下去,剩下的是 UPDATE t_order 和审计写入,属于业务本身就重的,后面再分批啃。

KDDM 我们也跑了一遍,相当于让机器帮我们交叉验证。它把我们手工发现的问题又点了一遍:缺索引、长事务持锁,还顺手确认了 shared_buffers 命中率没问题。机器和人的结论一致,心里才真踏实。

到周末下午,核心接口基本稳了:首页报表从 12.4 秒掉到 1.2 秒,订单查询从 8.5 秒掉到 0.9 秒,统计汇总从 21.3 秒掉到 2.8 秒。CPU 和 I/O 也不再抽风。

回头看,这套排查路径——连库、抓热点、看计划、改 SQL、建索引、调参数、查锁、KWR、KDDM——在金仓上跑下来,和 Oracle 的 AWR/ASH 思路其实是一脉相承的,只是工具名字换了。

有几个坑当时没写进正式报告,但印象很深:

  • pg_dump 不带走统计信息,迁移完第一件事就是全库 ANALYZE,不然优化器是瞎的。
  • 参数化查询在金仓上对常量值很敏感,ksql 里测的计划,和业务绑定变量跑出来的可能不一样。
  • 连接池 max_connections 配小了,高峰期连接打满,请求在应用层排队,看起来像数据库慢。
  • KWR 和 KDDM 的权限得提前开,不然到时候查不了,只能干瞪眼。

这些零碎问题,单独看都不致命,但凑在一起,足够把人搞得很狼狈。调优这活,很多时候不是靠某个大招,而是把这些细节一个一个抠过去。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_82648291/article/details/163614977

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--