B-tree 索引失效的 7 种常见姿势(附验证方法)

发布:2026-10-02 · 索引性能优化实战


「我建了索引为什么不走?」是 PG 性能问题的第一名。以下 7 种姿势覆盖了绝大多数情况, 每条都可以用 EXPLAIN 验证(配合本站分析器食用更佳)。

1. 违反最左前缀(联合索引)

CREATE INDEX idx ON orders (tenant_id, created_at);
-- ✅ 走:tenant_id 或 tenant_id + created_at
SELECT * FROM orders WHERE tenant_id = 1 AND created_at > now() - interval '7 days';
-- ❌ 不走:缺少前导列
SELECT * FROM orders WHERE created_at > now() - interval '7 days';

2. 对索引列做函数/表达式包裹

-- ❌
SELECT * FROM users WHERE lower(email) = 'a@b.com';
-- ✅ 建表达式索引,或改写为不包裹的等值条件
CREATE INDEX idx_users_email_lower ON users (lower(email));

3. 隐式类型转换

-- phone 是 text:
-- ❌ varchar 列与 text 比较可走索引;但 int 列与 '123' 比较时需要确认两侧类型一致
SELECT * FROM users WHERE id = '123';      -- id 是 bigint,'123' 会被转换,通常仍可走索引
SELECT * FROM users WHERE phone = 13800000000;  -- phone 是 text,数字被 cast 成 text?不,是列被 cast → 索引失效

口诀:让常量去迁就列的类型,而不是让列被转换。

4. LIKE 以通配符开头

-- ✅ 走(C collation 或 text_pattern_ops 下前缀匹配)
SELECT * FROM users WHERE name LIKE 'zhang%';
-- ❌ 不走
SELECT * FROM users WHERE name LIKE '%san%';

后缀/中间匹配需要 pg_trgm 的 GIN 索引。

5. 谓词「不像索引能帮上的样子」

优化器是基于成本决策的:预估要返回 30% 以上的行时,顺序扫描比反复走索引便宜, 不走索引反而是对的。先看估算行数,再决定要不要「修」它。

6. 统计信息严重过期

估算行数离谱(差 10 倍以上)时,优化器会做出错误决策。 ANALYZE 一下,很多「玄学」当场消失。

7. 索引无效(invalid)或事务快照未提交

并发建的索引可能处于 invalid 状态(比如 CREATE INDEX CONCURRENTLY 失败后残留), 优化器不会使用它。排查:

SELECT indexrelid::regclass AS index, indisvalid, indisready
FROM pg_index
WHERE NOT indisvalid;

验证方法论

  1. EXPLAIN (ANALYZE, BUFFERS) 看真实执行;
  2. 先确认「估算 vs 实际」,统计不准时一切分析免谈;
  3. 再看是否命中上面 7 条;
  4. 最后才考虑 CREATE INDEX 的新方案。

把计划粘进EXPLAIN 中文分析器,第 1、2 步它会自动帮你完成。


本文为 pgcn.cc 原创内容,转载需授权并保留链接。勘误/投稿: 联系方式。