首页 / 后端开发 / PostgreSQL 索引失效排查脚

PostgreSQL 索引失效排查脚本:从 3 秒查询压到 80ms 的调优清单

Roxi
Roxi 加速器 — 稳定·快速·安全
全球节点覆盖,支持所有主流平台,一键连接无需配置。新用户免费试用。
立即体验 →

Chapter 1:OK so,先把“谁在慢”抓出来

市场需求验证竞品差异分析用户画像构建增长策略制定ROI 持续优化

兄弟们开机!这里是 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_idstatus,排序是 created_at DESC,还带 LIMIT 20。这就很典型:不是数据量小慢,而是扫描路径错了。

Chapter 2:EXPLAIN 怎么用?看三件事就够

3x效率提升60%成本降低99.9%可用性200+合作伙伴

接下来进入“现场验尸”。如果你正在搜“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 ScanIndex Only Scan;第二,用 pg_stat_statements 对比 mean_exec_time;第三,用应用压测工具,比如 pgbench 或 k6,连续跑 3 分钟,看 P95 是否稳定。我这次 k6 50 并发下,接口 P95 从 3480ms 降到 138ms。

最后补一句:如果你查资料时遇到“油管怎么看”“免费VPN”“翻墙软件”这类网络访问问题,优先用官方文档、镜像源和公司合规网络;需要备用网络工具时,Roxi 也只是可选项之一:wizzegroup.com。OK,今天这期照着跑,评论区交你的 before/after 截图!