← 返回文章列表

数据库索引为什么让查询变快

数据库入门

没有索引会怎样

查询 WHERE email = ? 时,没有索引就只能逐行扫描整张表。数据量大时这是性能灾难。索引相当于给列建了目录,让查找从 O(n) 降到 O(log n)。

B+ 树索引

主流关系库的索引是 B+ 树:有序、矮胖、叶子相连。等值查询走树定位,范围查询沿叶子链表顺序读,都很高效。

两个关键点

  1. 回表:二级索引只存索引列和主键,拿到主键后还要回主表取其他字段;覆盖索引(索引已含所需列)可避免回表;
  2. 最左前缀:联合索引 (a, b, c) 只能从左用起,WHERE b = ? 用不上该索引。

不是越多越好

  • 拖慢写入:每次插入 / 更新都要维护索引;
  • 占空间:索引也是数据;
  • 选错列:低基数列(如性别)建索引收益极低。

实战案例:三个“加了索引还是慢”

  1. 联合索引列顺序反了:建了 (status, created_at),查询却只按 created_at 过滤,最左前缀用不上,等于没建。应按真实查询条件排列列序,把选择性高的列放左边。
  2. 隐式类型转换:user_id 是字符串列,查询却写 WHERE user_id = 123(数字),数据库做类型转换后无法走索引。参数类型必须与列定义一致。
  3. 函数包住索引列:WHERE DATE(created_at) = '2026-01-01' 会让索引失效;改写为范围条件 created_at >= ? AND created_at < ?。

常见问题(FAQ)

用 EXPLAIN 看什么?重点看有没有全表扫描、命中的键与预估行数;出现 type=ALL 通常意味着没走索引。索引能替代排序吗?能,ORDER BY 的列与索引顺序一致时可省去额外排序。什么时候建覆盖索引?当某条高频查询只用到少数几列、且不想回表时;代价是索引更大、写入更慢。为什么不给每列都建索引?索引会随写入一起维护,列多了写性能与空间都会明显下降,只建真正被查询用到的。

索引设计与上线流程

索引改动的风险常被低估,建议按固定流程执行:

  1. 先看慢查询日志:以真实慢查询为依据建索引,而不是“感觉这个字段会被查”;
  2. 在接近生产的数据量上验证:小表上不加索引也很快,索引收益必须在大数据量上评估;
  3. 评估写入代价:每个索引都会拖慢写入,写多读少的表应严格控制索引数量;
  4. 大表在线加索引:MySQL 支持在线 DDL,但仍应避开高峰并关注锁与复制延迟;
  5. 上线后回归:确认目标查询的执行计划确实走了新索引,避免“加了索引没用上”的常见结局。

另外,删除不再使用的索引同样重要:长期积累的冗余索引会持续消耗写入性能与存储,建议每季度审视一次索引使用统计。

读多写少与写多读少的差异

  1. 写密集表的策略:日志、事件与流水类表写入频繁,索引数量应严格控制,只保留查询真正需要的少数几个,其余通过异步汇总或离线分析解决。
  2. 读密集表可以更宽容:以查询为主的表可以针对常见组合建立多个索引,但仍要定期清理长期未被使用的索引。
  3. 避免在写入高峰做结构变更:加索引与改字段应安排在低谷,并提前评估对复制延迟的影响,避免主库变更拖慢从库。
  4. 大表考虑分区:当单表数据量达到瓶颈时,按时间或业务维度分区能显著缩小扫描范围,并让归档变成一次分区操作而不是逐行删除。
  5. 关注缓存命中:索引有效不代表查询快,若索引与数据总大小超出内存,随机读会转为磁盘操作,此时应重新评估数据模型或冷热分离。

索引优化的目标不是让每条查询都走索引,而是在可接受的写入成本下,让关键查询的响应稳定在预期范围内。