
你知道要给查询加索引。但加在哪、加几个、怎么加——你不确定。你遇到过这种情况:加了一个索引,查询快了;又加了一个,写入慢了。你开始怀疑,索引是不是越多越好?
数据量小的时候,索引的作用不明显。等数据量上来了,索引的收益和代价同时放大。设计合理的索引,需要对查询模式有清晰的理解。
索引的核心原理:B+树
MySQL InnoDB使用B+树作为索引的数据结构。B+树是一种多路平衡查找树,它的设计目标是减少磁盘I/O次数。一个深度为3的B+树可以存储约2000万条记录,意味着查找一条记录只需要3次磁盘I/O,而不需要扫描全部2000万行。
B+树的叶子节点存储了完整的数据行(聚簇索引)或主键值(二级索引)。叶子节点之间通过链表连接,支持范围查询的快速遍历。当你执行BETWEEN或> <查询时,数据库在B+树中找到起点,然后沿链表一路读取。
索引的核心代价在于维护。每次插入、更新、删除操作都需要更新所有相关索引,这个过程称为索引维护。索引数量越多,写入性能下降越明显。这就是为什么“索引不是越多越好”。
索引选择原则
原则一:为高频查询的WHERE条件建索引
查询中使用WHERE子句过滤数据,数据库需要定位满足条件的行。如果条件列没有索引,数据库执行全表扫描。给WHERE中使用的列建索引,数据库可以直接定位到目标数据位置。
原则二:为JOIN关联列建索引
多表JOIN时,被驱动表的关联列应该有索引。不带索引时,数据库可能对每一行都执行一次全表扫描。给被驱动表的关联列建索引,JOIN效率可提升数倍。
原则三:为ORDER BY和GROUP BY建索引
排序和分组操作如果没有索引支持,数据库需要将所有数据加载到内存中进行排序(Using filesort)。排序操作数据量大时,可能使用磁盘临时表,写入速度远慢于内存操作。给排序列建索引,数据库可以直接按索引顺序读取数据,避免额外排序。
原则四:选择区分度高的列
索引列的数据分布决定了索引的效率。区分度低的列(如性别只有两个值),索引无法有效缩小查询范围,数据库可能仍然需要扫描大量数据。区分度高的列(如用户ID、订单号)索引效率更高。
sql
-- 区分度计算 SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
值越接近1,区分度越高,适合建索引。
复合索引的设计
复合索引是在多个列上建立的索引。列的顺序决定索引能否被使用。
最左前缀匹配原则:复合索引(a, b, c),查询条件包含a时索引生效,包含a和b时索引生效,包含a、b、c时索引完全生效。只包含b或c不生效。
等值查询列放前面,范围查询列放后面:WHERE a = 1 AND b > 10,a是等值查询,b是范围查询。复合索引(a, b)优于(b, a),因为a先过滤数据,b在过滤后的结果上执行范围扫描。如果顺序反了,b的范围查询会在索引中扫描大量数据,效率下降。
覆盖索引:如果查询的所有列都在索引中,数据库不需要回表读取数据行,直接从索引返回结果。SELECT id, name FROM users WHERE age > 18,如果索引覆盖了id、name、age三列,MySQL可以直接从索引中获取数据,而无需访问磁盘上的数据行。
索引设计检查清单
| 检查项 | 建议 |
|---|---|
| WHERE条件中的高频列 | 建索引 |
| JOIN关联列 | 建索引 |
| ORDER BY列 | 建索引 |
| GROUP BY列 | 建索引 |
| 区分度<10%的列 | 不建索引 |
| 频繁更新的列 | 不建索引 |
| 小表(<1000行) | 不建索引 |
一个真实案例
一张订单表有500万行数据。原始查询SELECT * FROM orders WHERE user_id = 12345 ORDER BY created_at DESC耗时约8秒。慢查询日志显示该查询未使用任何索引。
加入复合索引(user_id, created_at)后,查询命中索引,执行计划显示type=ref,rows从500万变为12条。查询耗时从8秒降至0.02秒。
最后一句
索引设计没有标准答案。但有一个原则是确定的:只为查询服务。不要为所有列加索引,只为查询条件涉及的列加。先看慢查询日志,找到真正慢的SQL,再用EXPLAIN分析执行计划,然后决定在哪里加索引。加完索引后再次EXPLAIN,验证效果。如果索引对写入有明显影响,考虑删除冗余索引。索引的重建和维护成本在数据量增长时会被放大。定期审查,定期优化,索引才会持续服务你的查询。




