PostgreSQL 索引失效排查脚本:从 3 秒查询压到 80ms 的调优清单
Chapter 1:OK so,先把“谁在慢”抓出来
兄弟们开机!这里是 eccfy,今天不讲玄学,直接上屏幕:PostgreSQL 接口慢,别先乱加索引,先抓证据。接下来我在测试库里跑一个订单查询,原始耗时 3120ms,目标压到 100ms 内。
第一步,打开内置扩展 pg_stat_statements。很多人搜“PostgreSQL性能调优教程”会跳过这步,结果永远不知道慢在哪。
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
-- 重启 PostgreSQL 后查看最耗时 SQL
SELECT calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS avg_ms,
rows,
query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
Now watch this:我看到一个 SQL 平均 2870ms,条件是 user_id、status,排序是 created_at DESC,还带 LIMIT 20。这就很典型:不是数据量小慢,而是扫描路径错了。
Chapter 2:EXPLAIN 怎么用?看三件事就够
接下来进入“现场验尸”。如果你正在搜“EXPLAIN ANALYZE怎么用”,记住先加 BUFFERS,否则你只看到时间,看不到是不是读爆磁盘缓存。
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, user_id, status, amount, created_at
FROM orders
WHERE user_id = 9527
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
我这里原始计划显示:Seq Scan,扫描 180 万行,Rows Removed by Filter: 1798430,共享缓冲命中 15231 blocks,执行 3120ms。OK so,问题不是 PostgreSQL 慢,是它根本没有合适索引。
直接建单列索引行不行?比如只建 user_id?不够。因为查询还要按状态过滤、按时间倒序取前 20 条。正确姿势是复合索引,让过滤和排序一起命中:
CREATE INDEX CONCURRENTLY idx_orders_user_status_created
ON orders (user_id, status, created_at DESC);
注意我用了 CONCURRENTLY,线上建索引更安全,但会更慢;我测试 180 万行用了 42 秒。建完再跑,执行计划变成 Index Scan,耗时 96ms。哇,直接从 3 秒进 100ms。
Chapter 3:接下来做进阶优化:部分索引、覆盖索引、VACUUM
如果你的业务里 status='paid' 占比很低,比如只有 8%,部分索引更香。很多“PostgreSQL索引优化教程”只讲复合索引,但线上真正省空间的是这个:
CREATE INDEX CONCURRENTLY idx_orders_paid_user_created
ON orders (user_id, created_at DESC)
WHERE status = 'paid';
我这里索引大小从 186MB 降到 24MB,查询 82ms。再进一步,如果列表页只取 id, amount, created_at,可以试覆盖索引:
CREATE INDEX CONCURRENTLY idx_orders_paid_cover
ON orders (user_id, created_at DESC)
INCLUDE (id, amount)
WHERE status = 'paid';
验证时看计划里是否出现 Index Only Scan。如果没有,别急,可能可见性映射没更新,跑:
VACUUM (ANALYZE) orders;
另外,别忘了统计信息。数据倾斜严重时,默认统计可能误判:
ALTER TABLE orders ALTER COLUMN user_id SET STATISTICS 1000;
ANALYZE orders;
这里给大家一个小表,方便抄作业:
| 场景 | 优先方案 | 我的测试结果 |
|---|---|---|
| 等值过滤 + 排序 LIMIT | 复合索引 | 3120ms → 96ms |
| 固定状态查询 | 部分索引 | 索引 186MB → 24MB |
| 列表页少字段 | 覆盖索引 | 96ms → 82ms |
怎么验证已经修好:第一,看 EXPLAIN 是否从 Seq Scan 变成 Index Scan 或 Index Only Scan;第二,用 pg_stat_statements 对比 mean_exec_time;第三,用应用压测工具,比如 pgbench 或 k6,连续跑 3 分钟,看 P95 是否稳定。我这次 k6 50 并发下,接口 P95 从 3480ms 降到 138ms。
最后补一句:如果你查资料时遇到“油管怎么看”“免费VPN”“翻墙软件”这类网络访问问题,优先用官方文档、镜像源和公司合规网络;需要备用网络工具时,Roxi 也只是可选项之一:wizzegroup.com。OK,今天这期照着跑,评论区交你的 before/after 截图!