MySQL 索引是提升查询性能最直接的手段,但用不好反而会拖慢写入甚至导致全表扫描。这篇文章从底层数据结构出发,聊聊索引到底是怎么工作的,以及日常开发中最容易踩的坑。
B+树:InnoDB 索引的核心
InnoDB 默认使用 B+树作为索引的数据结构。与普通二叉树不同,B+树是一棵多路平衡搜索树,每个节点可以存放多个键值,树的高度通常只有 3~4 层。这意味着即使表中有上千万行数据,通过主键查找一条记录最多也只需要 3~4 次磁盘 I/O。
B+树的几个关键特点:
- 所有数据都存储在叶子节点,非叶子节点只存储索引键值和指针,这使得单个页能容纳更多的索引条目,降低树的高度。
- 叶子节点之间通过双向链表连接,天然支持范围查询和排序操作——这也是为什么
ORDER BY主键时几乎不需要额外排序。 - 聚簇索引与二级索引的区别:InnoDB 的主键索引(聚簇索引)叶子节点直接存储完整的行数据;而二级索引的叶子节点存储的是主键值,查询时需要先在二级索引中定位到主键,再通过主键索引找到完整行——这就是常说的"回表"。
索引的四种类型
-
主键索引(PRIMARY KEY):每张表只能有一个,不允许 NULL,InnoDB 会用它来组织整张表的物理存储。如果建表时没有显式指定主键,InnoDB 会选择第一个非空唯一索引,或者自动生成一个隐藏的 6 字节 ROW_ID。
-
唯一索引(UNIQUE):列值必须唯一,但允许 NULL。适合用在业务上有唯一性约束的字段,比如手机号、身份证号。
-
普通索引(INDEX):最基本的索引,没有任何约束。适用于频繁出现在 WHERE、JOIN 条件中的字段。
-
联合索引(Composite Index):在多个列上建立的索引,遵循"最左前缀"原则。例如
INDEX(a, b, c)可以覆盖WHERE a=1、WHERE a=1 AND b=2、WHERE a=1 AND b=2 AND c=3的查询,但无法直接用于WHERE b=2或WHERE c=3。
索引失效的常见场景
在实际开发中,以下几种写法会让索引形同虚设:
- 对索引列使用函数或表达式:
WHERE YEAR(create_time) = 2020无法走create_time上的索引,应改为范围查询WHERE create_time >= '2020-01-01' AND create_time < '2021-01-01'。 - 隐式类型转换:字段是
varchar类型却传入数字进行比较,MySQL 会对字段做隐式转换,导致索引失效。 - LIKE 以通配符开头:
WHERE name LIKE '%张'无法使用索引,WHERE name LIKE '张%'则可以。 - 联合索引不满足最左前缀:跳过了联合索引的第一个字段直接查后面的字段。
- OR 条件中包含未索引列:
WHERE a=1 OR b=2,如果 b 没有索引,整个查询会退化为全表扫描。 - NOT IN / NOT EXISTS:在某些情况下优化器会放弃使用索引。
用 EXPLAIN 诊断查询
遇到慢查询时,第一步永远是在 SQL 前面加上 EXPLAIN。重点关注以下几列:
- type:访问类型,从优到劣依次为
system > const > eq_ref > ref > range > index > ALL。如果看到ALL就意味着全表扫描,需要优化。 - key:实际使用的索引名,如果为 NULL 说明没走索引。
- rows:MySQL 预估需要扫描的行数,越小越好。
- Extra:额外信息。
Using index表示覆盖索引(不需要回表);Using filesort表示额外排序;Using temporary表示使用了临时表——后两者通常意味着有优化空间。
小结
索引不是越多越好。每个索引都会占用额外的磁盘空间,并且在 INSERT/UPDATE/DELETE 时需要同步维护。合理的做法是根据实际的查询模式来设计索引:优先为 WHERE、JOIN、ORDER BY 中高频出现的列建立索引,善用联合索引减少索引数量,并定期通过慢查询日志 + EXPLAIN 来持续优化。