没有索引会怎样
查询 WHERE email = ? 时,没有索引就只能逐行扫描整张表。数据量大时这是性能灾难。索引相当于给列建了目录,让查找从 O(n) 降到 O(log n)。
B+ 树索引
主流关系库的索引是 B+ 树:有序、矮胖、叶子相连。等值查询走树定位,范围查询沿叶子链表顺序读,都很高效。
两个关键点
- 回表:二级索引只存索引列和主键,拿到主键后还要回主表取其他字段;覆盖索引(索引已含所需列)可避免回表;
- 最左前缀:联合索引
(a, b, c)只能从左用起,WHERE b = ?用不上该索引。
不是越多越好
- 拖慢写入:每次插入 / 更新都要维护索引;
- 占空间:索引也是数据;
- 选错列:低基数列(如性别)建索引收益极低。
实战案例:三个“加了索引还是慢”
- 联合索引列顺序反了:建了
(status, created_at),查询却只按created_at过滤,最左前缀用不上,等于没建。应按真实查询条件排列列序,把选择性高的列放左边。 - 隐式类型转换:
user_id是字符串列,查询却写WHERE user_id = 123(数字),数据库做类型转换后无法走索引。参数类型必须与列定义一致。 - 函数包住索引列:
WHERE DATE(created_at) = '2026-01-01'会让索引失效;改写为范围条件created_at >= ? AND created_at < ?。
常见问题(FAQ)
用 EXPLAIN 看什么?重点看有没有全表扫描、命中的键与预估行数;出现 type=ALL 通常意味着没走索引。索引能替代排序吗?能,ORDER BY 的列与索引顺序一致时可省去额外排序。什么时候建覆盖索引?当某条高频查询只用到少数几列、且不想回表时;代价是索引更大、写入更慢。为什么不给每列都建索引?索引会随写入一起维护,列多了写性能与空间都会明显下降,只建真正被查询用到的。
索引设计与上线流程
索引改动的风险常被低估,建议按固定流程执行:
- 先看慢查询日志:以真实慢查询为依据建索引,而不是“感觉这个字段会被查”;
- 在接近生产的数据量上验证:小表上不加索引也很快,索引收益必须在大数据量上评估;
- 评估写入代价:每个索引都会拖慢写入,写多读少的表应严格控制索引数量;
- 大表在线加索引:MySQL 支持在线 DDL,但仍应避开高峰并关注锁与复制延迟;
- 上线后回归:确认目标查询的执行计划确实走了新索引,避免“加了索引没用上”的常见结局。
另外,删除不再使用的索引同样重要:长期积累的冗余索引会持续消耗写入性能与存储,建议每季度审视一次索引使用统计。
读多写少与写多读少的差异
- 写密集表的策略:日志、事件与流水类表写入频繁,索引数量应严格控制,只保留查询真正需要的少数几个,其余通过异步汇总或离线分析解决。
- 读密集表可以更宽容:以查询为主的表可以针对常见组合建立多个索引,但仍要定期清理长期未被使用的索引。
- 避免在写入高峰做结构变更:加索引与改字段应安排在低谷,并提前评估对复制延迟的影响,避免主库变更拖慢从库。
- 大表考虑分区:当单表数据量达到瓶颈时,按时间或业务维度分区能显著缩小扫描范围,并让归档变成一次分区操作而不是逐行删除。
- 关注缓存命中:索引有效不代表查询快,若索引与数据总大小超出内存,随机读会转为磁盘操作,此时应重新评估数据模型或冷热分离。
索引优化的目标不是让每条查询都走索引,而是在可接受的写入成本下,让关键查询的响应稳定在预期范围内。