Postgres 索引选错的代价
昨天早上收到告警,一个平时 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。
经验
- 低基数列单独建索引基本没用,除非配合部分索引
- autovacuum 的 analyze 在大表上可能跟不上,关键表可以手动 ANALYZE
- CREATE INDEX 一定加 CONCURRENTLY,不然锁表