木杉笔记

记录后端、运维与数据库的踩坑日常

Postgres 索引选错的代价

2026-08-28数据库

昨天早上收到告警,一个平时 40ms 的查询变成了 4s。数据量没变,代码没改,那只能是计划变了。

EXPLAIN 一下

EXPLAIN ANALYZE
SELECT * FROM orders
WHERE status = 'pending' AND created_at > now() - interval '1 day';

结果走了 idx_orders_status,一个建在 status 上的普通 B-tree。

问题在哪

status 这个列一共就五个取值,而 'pending' 占了大头。这种低基数列建 B-tree,Postgres 扫索引之后还是要回表取大量行,还不如直接顺序扫。

更麻烦的是统计信息过期了,优化器以为 'pending' 只占 2%,实际占了 60%。

怎么修

-- 先让统计信息更新
ANALYZE orders;

-- 换成复合索引,把高基数列放前面
CREATE INDEX CONCURRENTLY idx_orders_created_status
  ON orders (created_at DESC, status)
  WHERE status = 'pending';

部分索引 + 正确的列顺序,查询回到 35ms。

经验