PostgreSQL 实战:索引、执行计划与一次慢查询优化

Words: 781Read Time: 2 minLast edited: 2026-8-4
博客的归档页最近越来越慢,点开要转两秒圈。这个页面按"状态加发布时间"筛文章再倒序分页,SQL 简单得不能再简单,但慢就是慢。我用 EXPLAIN ANALYZE 扒了一把执行计划,最后只加了一个复合索引,查询时间从 1200ms 干到 3ms。记录一下过程。

先还原现场

表是 articles,十几万行(博客文章加上历史导入的数据),慢查询长这样:
肉眼看着人畜无害:条件就两个,还 LIMIT 20。但 PostgreSQL 不看你 LIMIT 多少——它要先找到所有满足条件的行、排好序,才能给你前 20 条。

EXPLAIN ANALYZE 看执行计划

给这条 SQL 前面加上 EXPLAIN ANALYZE 再跑一遍,输出里关键的几行:
两个信号一目了然:Seq Scan 是全表扫描,十几万行逐行过;过滤完还剩八万五千行,拿去排序,甚至排到了磁盘上(external merge Disk)。慢得有理有据。
注意 ANALYZE 会真实执行一遍 SQL。SELECT 无所谓,但如果分析的是 UPDATE 或 DELETE,记得套个事务再 ROLLBACK,别在生产上直接跑。

加复合索引

条件和排序都明确了,索引照着建:
再跑 EXPLAIN ANALYZE,执行计划变成了 Index Scan,actual time 个位数毫秒。原理是:索引里 status 相同的行天然按 created_at 有序存储,PostgreSQL 定位到 'published' 那一段,按时间倒着扫 20 条满足日期条件的就收工,排序步骤直接消失。1200ms 到 3ms,就是这么朴实无华。

最左前缀原则

B-tree 复合索引有个铁律:最左前缀。(status, created_at) 这个索引:
  • WHERE status = 'published',能用
  • WHERE status = ... AND created_at > ...,完美匹配
  • WHERE created_at > '2024-01-01',单独用基本用不上——索引是先按 status 排的,跳过第一列就像查字典不按首字母
所以建索引前要把真实查询场景列出来,列的顺序按"等值在前、范围在后"放。也别觉得索引多多益善:每个索引都会拖慢写入、占磁盘,够用就好。

心得

这次优化最大的收获不是那句 CREATE INDEX,而是一个习惯:慢查询别猜,先 EXPLAIN。执行计划里 Seq Scan、Rows Removed by Filter、排序是否落盘,这几个关键词扫一眼,八成的问题就有方向了。另外推荐开一下 pg_stat_statements 扩展,它能统计所有 SQL 的执行耗时排行,慢查询不用等用户投诉,自己就会浮出来。
Loading...
© 2024 - 2026 ihuadz
中文