优化实战:救活三条慢查询
交付日:从 pg_stat_statements 里挑出最慢的三条,把它们救活,写成可以进简历的案例。
学 · 10 min
练 · 85 min
- 从 pg_stat_statements 挑出 3 条秒级查询作为目标
参考答案
优先挑「总耗时高 + 平均耗时秒级」的:总耗时高说明它真的在吃数据库,优化它收益最大。没跑过几条查询榜单是空的,先把本周的练习各跑几遍。
-- 前提:D41 已装好 pg_stat_statements(preload + 重启 + create extension) select calls, round(total_exec_time::numeric, 0) as 总ms, round(mean_exec_time::numeric, 1) as 平均ms, rows, left(query, 70) as query from pg_stat_statements where mean_exec_time >= 1000 -- 平均耗时 ≥1 秒才算「秒级」 order by total_exec_time desc limit 10; - 逐条记录优化前的 EXPLAIN ANALYZE 和耗时
参考答案
基线三件套一个都不能少:耗时(\timing / Execution Time)、执行计划、返回行数--没有基线,优化完说不出「快了多少倍」,这是本日铁律,也是过纲里「简历素材」的数字来源。
\timing on -- 例:近 30 天日报(换成你自己挑的目标查询) explain (analyze, buffers) select created_at::date as 日期, count(*) as 单数, sum(total_amount) as gmv from orders where created_at >= current_date - 30 group by 1; - 提出假设 -> 加索引或改写 SQL -> 验证
参考答案
方法论:一次只改一个变量(只加一个索引或只改一处写法),改完立刻重新 explain--同时动两处就说不清是哪个手段起的作用。假设不成立就回滚(drop index / 还原 SQL),再提下一个假设。
-- 假设示例 A:「某用户的某时间段」没索引 -> 建复合索引(等值在前、范围在后) create index idx_orders_uid_created on orders (user_id, created_at); -- 假设示例 B:where date(created_at) = ... 函数包列失效 -> 改左闭右开范围 -- 验证:同一个查询重跑 explain (analyze, buffers) select * from orders where user_id = '00000000-0000-0000-0000-000000000042' and created_at >= current_date - 30; - 记录优化后的计划与耗时,算出提升倍数
参考答案
提升倍数 = 优化前耗时 ÷ 优化后耗时,用 Execution Time 算,别用 cost(它不是秒)。注意缓存影响:第一次可能带冷读,多跑两三次取稳定值再记录。
\timing on -- 与基线一模一样的查询再跑一次 explain (analyze, buffers) select created_at::date as 日期, count(*) as 单数, sum(total_amount) as gmv from orders where created_at >= current_date - 30 group by 1; - 整理成对比表格:查询 | 优化前 | 优化后 | 手段 | 提升倍数
参考答案
表格列就用题面那五列:查询 | 优化前 | 优化后 | 手段 | 提升倍数。示例一行:「近 30 天日报 | ≈1s | ≈100ms | created_at 加索引 | ≈10×」。每行都要能附上优化前后的执行计划--面试官追问「为什么快了」,拿计划说话,不拿感觉说话。