目标
按时长排序修的是戏剧化的离群值,按总耗时(调用数 × 均值)排序修的才是负载。热循环里 50 毫秒的查询远比 4 秒的日报重要,只有总耗时视图能揭示这一点。修完每个惯犯都要在生产量级的数据上验证计划成立,并加计划回归守卫。
步骤要点
- 按总耗时排名:聚合语句统计按 total_exec_time 排序,前几行就是数据库时间的最大占用者。
- 分析头部惯犯:对生产量级数据跑 EXPLAIN (ANALYZE),指出计划缺陷——缺索引、行数误估、排序溢出。
- 精准修复缺陷:谓词形态匹配的复合索引、消除包裹列的函数、游标分页替代 offset 扫描。
- 生产形态下验证:在预发用生产量级数据加流量回放测试,确认计划在真实基数下依然成立。
- 带回归守卫上线:部署后观察该语句总耗时下降,并对触碰过的查询加计划回归检查。
排错提示
lower(name) 这类列上函数让索引静默失效,查询能跑能返回,却在总耗时榜首坐几个月;深翻页 offset 读取并丢弃五万行,每翻一页线性变差,换游标分页让深页恒定成本;修复在预发有效生产失效是基数不同,验证必须用生产量级数据。
适用边界
总耗时排名依赖统计累积周期,刚重启的库数据不足需等待;索引修复增加写放大,写密集表要一并评估写入代价。
代码示例
-- ranked by total time
SELECT round(total_exec_time) AS total_ms,
calls, round(mean_exec_time, 1) AS mean_ms,
left(query, 60) AS q
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 5;
-- 412,088 102,411 4.0 SELECT * FROM items
-- WHERE lower(name) = $1
-- 88,214 14,310 6.2 ... offset pagination ...
-- fix #1: the lower() wraps the column -> index unusable
-- functional index: CREATE INDEX ON items (lower(name));
-- mean 4.0ms -> 0.11ms total: 412s -> 11s per day
-- fix #2: keyset pagination replaces OFFSET deep scans