
那天早上九点,电话就开始响。省级平台刚切到新库,首页那张综合报表转了十几秒,点查询直接卡死,前端弹了一堆“请求失败”。业务部门没明说,但那意思我听得懂: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




