凌晨两点,生产环境告警。某个查询从200毫秒飙到15秒,你加了个索引,结果更慢了。
这不是段子。我见过太多开发者把索引当万能药,最后发现药不对症。PostgreSQL的索引机制比你想象的复杂——它既能救你,也能坑你。
索引的本质:空间换时间的精确计算
没有索引时,PostgreSQL做顺序扫描(sequential scan)。100行无所谓,1000万行就是灾难。
B树索引把数据变成三层结构:根节点划大范围,内部节点收窄,叶子节点存真实值和行指针。查找时从根往下走,复杂度从O(n)降到O(log n)。
但代价是什么?
每次INSERT、UPDATE、DELETE都要维护这棵树。写操作变慢,存储空间增加。索引不是免费午餐,是精确计量的交易。
正方:索引解决一切性能问题
这个阵营的观点很直接:查询慢?加索引。邮件查询慢?给email列建索引。时间范围查询慢?给created_at建索引。
他们手里有EXPLAIN ANALYZE的截图。顺序扫描变索引扫描,执行时间从8.2秒降到0.4毫秒。数字漂亮,结论明确。
复合索引(composite index)更是他们的利器。WHERE email = 'x' AND status = 'active',单列索引可能只用到第一个条件,复合索引能覆盖整个过滤逻辑。
部分索引(partial index)和表达式索引(expression index)则是进阶武器。只给活跃用户建索引,或者给LOWER(email)建索引解决大小写匹配问题。
听起来完美。
反方:索引是性能陷阱的温床
另一群人见过太多索引灾难。
第一个陷阱:索引选择性。性别字段只有男女两个值,B树查完还是要回表取大半数据。这时候索引比全表扫描还慢——多了索引查找的开销。
第二个陷阱:写放大。一个UPDATE要改数据页,还要改所有相关索引页。索引越多,写操作越重。我见过一个表有17个索引,INSERT性能暴跌70%。
第三个陷阱:最左前缀的误解。复合索引(a,b,c),查询WHERE b = 2完全用不上。很多人建完索引发现没生效,却不知道为什么。
第四个陷阱:维护成本。VACUUM、ANALYZE、REINDEX,这些操作在大表上可能锁表数小时。索引不是建完就完,是持续的技术债务。
我的判断:索引是手术刀,不是锤子
看完两边,结论很清楚。
索引有用,但只在特定条件下。你需要同时满足:查询频率高、数据量大、选择性好、写操作可承受。四个条件缺一个,就要重新评估。
具体操作建议:
先用EXPLAIN ANALYZE看真实执行计划,别猜。seq scan不一定慢,index scan不一定快。成本估算(cost)和实际时间都要看。
优先建复合索引覆盖多个查询,而不是给每个WHERE条件单列建索引。三个单列索引往往不如一个精心设计的复合索引。
定期用pg_stat_user_indexes检查索引使用频率。从没被用过的索引,删掉它。每个无用索引都在拖累你的写入性能。
大表加索引用CREATE INDEX CONCURRENTLY,避免锁表。生产环境的操作,宁可慢,不能停。
最后记住:索引解决的是"找数据"的问题。如果你的查询慢是因为JOIN太多、返回行数太大、或者计算逻辑太重,索引无能为力。
凌晨两点的那个告警,最后发现是缺失的外键索引导致嵌套循环JOIN爆炸。加了索引,查询回到200毫秒。但更重要的是,我们建立了索引审查流程——每次 schema 变更都要过一遍性能 checklist。
毕竟,最好的优化是从不让问题发生。而次好的优化,是知道为什么你的"优化"反而让事情更糟。
热门跟贴