最近几年我处理过的慢查询工单里,十个里有八个最终都能归结到同一条:索引没用好。要么是压根没建索引,要么是建了索引但SQL写法让优化器根本用不上。很多人一遇到慢查询就急着EXPLAIN看执行计划,却忽略了最底层的那个问题——MySQL的索引到底是怎么把数据找出来的。
如果你不了解B+树是怎么组织的,那“创建优化”这件事基本只能靠猜。你很难理解为什么联合索引要讲究最左前缀,为什么对索引列套一层函数就会失效,为什么有时候MySQL放着索引不用偏偏去全表扫。这篇文章我就从B+树原理开始,把索引的存储结构、创建策略、慢查询定位这条链路串起来讲透,最后再用一个完整的慢查询优化案例收尾。不管是刚入门的新手,还是写了好几年SQL但没系统梳理过索引逻辑的同学,应该都能从中拿到点能直接用的东西。
1. 为什么是B+树:从磁盘IO的限制说起
1.1 索引不是“目录”这么简单
很多人第一次接触索引,听到的类比是“就像书的目录”,这句话方向对,但很容易造成误解。书的目录只告诉你内容在第几页,而数据库索引不仅要做“定位”,还要频繁应对范围查询、排序、去重、分组这类操作。如果你只把它当成一个快速定位的目录,就很难解释为什么索引能同时帮ORDER BY加速,为什么范围查询也能走索引,为什么删除和更新也会变慢。
真正理解索引,要从存储介质讲起。MySQL的InnoDB引擎数据落在磁盘上,磁盘随机读比内存慢好几个数量级,因此所有索引结构的设计目标只有一个:尽量减少磁盘IO次数。而减少IO最有效的办法,就是让查询路径上经过的“树的高度”尽量低。
1.2 哈希结构为什么不行
有一类索引结构是哈希索引,它在等值查询上非常快,理论上是O(1)的复杂度。但它的致命弱点是:数据按哈希值存放,顺序被打乱,一旦遇到范围查询、前缀匹配、排序,哈希索引就完全无能为力。
这也解释了为什么InnoDB默认的索引结构选择了树而不是哈希。实际业务里大部分查询并不只是 WHERE id = 1,更多的是 WHERE create_time BETWEEN ... AND ...,或者是 ORDER BY age LIMIT 10。哈希索引在这些场景下连参与的资格都没有。
1.3 B树和B+树的关键差异
B树和B+树名字很像,但内部设计差异十分关键。B树在每个节点上都存储数据,而B+树只在叶子节点存储数据,非叶子节点全部用来存放索引键和指针。
这意味着同样的页面大小(InnoDB默认16KB),B+树能容纳更多的索引键,树的层数更矮。三层B+树能存放上千万甚至上亿条记录的主键索引,而如果换成B树,因为非叶子节点占了大量空间存数据,树的高度会明显增加,每次查询多一层就意味着多一次磁盘IO。
B+树还有一个杀手级设计:叶子节点之间用链表串联,并且按照索引键排序。这让范围查询变得极其简单——找到第一个符合条件的记录后,顺着链表往后扫描就行,不需要反复从根节点重新遍历。B树没有这个叶子链表,范围查询只能中序遍历,效率差得多。
1.4 一次索引查询发生了什么
假设有一张用户表,主键id上有聚簇索引,执行 SELECT * FROM user WHERE id = 9527:
- MySQL定位到B+树根节点所在的页,把它读入内存。
- 在根节点里比较索引键,确认下一步进入哪个子节点。
- 逐层向下,直到命中叶子节点。
- 在叶子节点中找到id = 9527的记录,由于InnoDB的叶子节点就是整行数据,直接返回。
整个过程大概3到4次IO,而且由于根节点页在内存中常驻,实际IO通常只有1到2次。这比全表扫描从第一页翻到最后一页要高效得多。
理解了这种结构以后,你就能自己推导出很多结论:索引键越短,单个页能装下的键越多,树越矮,IO越少;索引的选择性越高,B+树剪枝越快,扫描范围越小。这些结论会直接指导后面的索引创建优化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 聚簇索引、二级索引与回表:索引内部怎么配合
2.1 聚簇索引:数据和索引长在一起
InnoDB的表本质上就是一棵以主键为索引键的B+树,这棵树被称为聚簇索引,也常叫主键索引。它的叶子节点存的不只是主键值,而是整行完整记录。也就是说,在InnoDB里“数据即索引,索引即数据”,不存在一份独立于索引之外的数据文件。
这个设计有个直接后果:如果你建表时没指定主键,InnoDB会偷偷找一列非空的唯一索引作为主键,实在找不到就生成一个隐藏的rowid。所以“表必须有主键”不是规范要求,而是存储引擎层面强制存
