B+树是MySQL索引的首选结构,因其在磁盘I/O效率、范围查询支持和数据稳定性三方面优于哈希表、二叉树及B树:哈希索引仅支持等值查询且不支持范围扫描;B树非叶节点存数据导致树高更高、范围查询需频繁回溯;而B+树所有数据集中于有序链表连接的叶子节点,既降低树高减少I/O,又使范围查询和顺序遍历高效稳定。

B+树是MySQL索引的默认且事实标准结构,不是因为“它很酷”,而是因为磁盘IO、范围查询、并发稳定性这三件事,B树和Hash都扛不住。
为什么不用Hash索引做主索引?
Hash索引只适合=等值查询,遇到WHERE id > 100或ORDER BY created_at就直接退化成全表扫描。InnoDB根本不支持Hash作为主索引(虽然有自适应哈希索引,但那是内部优化,不可控)。MyISAM压根不支持Hash索引。另外,Hash碰撞会导致链表拉长,查询退化为线性扫描——这点在高基数字段上尤其明显。
常见错误现象:EXPLAIN显示type: ALL,明明建了索引却没走;SELECT ... WHERE status IN ('A','B')响应慢得反常。
- MyISAM和InnoDB都不允许你显式声明
HASH为主键索引类型 - 即使你在Memory引擎里用
HASH,也别指望它能替代B+Tree处理业务主键 - InnoDB的自适应哈希索引是自动触发的,无法配置,也不该依赖它解决设计问题
为什么不是B树而是B+树?
B+树把所有数据都塞进叶子节点,并让叶子节点用双向链表串起来。这样做的直接好处是:每次磁盘IO能读更多关键字(非叶节点不存数据,只存key),同时范围扫描时不用反复回溯父节点——顺着叶子链表扫就行。
举个实际影响:假设一个页(16KB)能存500个key,B树每个节点还要存data指针,可能只能放300个;B+树非叶节点纯key,就能放450个。树高降低一层,意味着一次范围查询少一次磁盘IO——这对SSD尚可忽略,对机械盘就是百毫秒级差异。
- B+树所有查询路径长度一致,不存在“运气好走左子树快、运气差走右子树慢”的情况
- 插入/删除时,B+树只需调整叶子链表指针,B树要频繁挪动data域,分裂代价更高
- InnoDB的聚簇索引要求数据物理有序,B+树天然适配这个约束
InnoDB和MyISAM的B+树到底差在哪?
核心就一条:InnoDB的主键索引叶子节点存的是**整行数据**,MyISAM的主键索引叶子节点存的是**行号(row number)或文件偏移量**。这就导致二者行为完全分化。
比如执行SELECT * FROM t WHERE name = 'lisi'(name是普通索引):
- MyISAM:先查
name索引树拿到行号 → 再按行号直接跳转到数据文件读整行(1次索引IO + 1次数据IO) - InnoDB:先查
name索引树拿到主键值(比如id=5)→ 再查主键索引树定位到整行数据(2次索引IO,无额外数据文件访问)
这就是常说的“InnoDB回表要查主键”——但注意,它不是去查另一个文件,而是再走一遍B+Tree。所以SELECT id, name这种覆盖索引查询,InnoDB连第二次索引查找都省了。
容易踩的坑:SELECT *在InnoDB上如果没走覆盖索引,等于强制触发二次B+Tree查找;而MyISAM哪怕SELECT *,只要索引存在,就是固定两次IO(索引+数据),跟字段数无关。
什么时候B+树也会失效?
B+树再好,也救不了设计失当的查询。最典型的是前导模糊匹配:LIKE '%abc'会让整个索引失效;函数包装字段:WHERE YEAR(created_at) = 2025,MySQL无法用索引做范围下推;还有隐式类型转换:WHERE user_id = '123'(user_id是INT)会放弃索引。
更隐蔽的问题是:InnoDB二级索引存储的是主键值,如果你用VARCHAR(255)甚至UUID做主键,那每个二级索引的叶子节点都要存一份超长主键——索引体积暴增,缓存命中率暴跌,查询反而变慢。
真正关键的不是“B+树多厉害”,而是你建的索引是否匹配查询模式、主键是否足够紧凑、有没有无意中写出索引无法下推的条件——这些地方一错,B+树再稳也没用。











