MySQL B+Tree索引是什么

来源:Java编程网作者:Canve头衔:草根站长
导读:本期聚焦于Canve创作的《MySQL B+Tree索引是什么》,敬请观看详情。如果直接问MySQL为什么默认选择B+Tree而不是红黑树或哈希表,答案往往藏在磁盘IO和范围查询这两个约束里。B+Tree是一种多路平衡搜索树,它的非叶子节点只保存键值和子节点指针,所有实际数据都存放在叶子节点,叶子节点之间用双向链表连接。这样的设计让树的高度变得很低,通常三到四层就能管理上千万行数据,极大减少随机磁盘读取。对MySQL InnoDB引擎来说,主键索引就是聚簇索引,叶子节点直接保存整行记录;二级索引叶子节点保存主键值,需要回表取数据。理解B+Tree的分裂、页结构和最左前缀规则,是判断索引是否生效、为什么范围查询后无法继续使用后续索引列的关键。这篇文章会从结构、存储和查询三个层面拆解B+Tree索引。

B+Tree索引并不是MySQL独有的概念,它是数据库和文件系统中常见的一种多路平衡树结构。MySQL InnoDB引擎把表数据和索引都组织在B+Tree中,主键索引的叶子节点保存的是整行数据,二级索引的叶子节点保存的是主键值。要理解为什么一条SQL能走索引、为什么有时索引会失效,首先要弄清楚B+Tree的节点结构和查找路径。

MySQL B+Tree索引是什么

B+Tree 的核心结构:为什么叶子要串成链表

B+Tree是一种多路平衡搜索树,和普通的二叉搜索树不同,它的每个节点可以容纳多个键值和多个子节点指针。非叶子节点只负责路由,不保存完整数据;所有真实的数据记录都落在叶子节点上,并且叶子节点之间通过双向链表连接。这个设计有两个直接好处:一是非叶子节点可以做得更小,单次磁盘页就能读取更多键值,树的高度随之降低;二是范围查询时,只要在叶子层定位到起始键,就可以顺着链表依次向后扫描,而不需要沿着树返回到上层节点。

一个B+Tree节点通常包含多个键值和指向下一层节点的指针,叶子节点还会额外保存指向相邻叶子节点的指针。下面是一个简化的节点结构示意,实际InnoDB的页结构要复杂得多,但核心字段基本相同。

typedef struct bplus_node {
    int is_leaf;
    int key_num;
    int keys[MAX_KEY];
    void *children[MAX_KEY + 1];
    struct bplus_node *next;
} bplus_node;

假设InnoDB页大小为16KB,非叶子节点中一个键值占8字节,一个指针占6字节,扣除页头等开销后,一个非叶子节点大约能放1170个键值和指针。三层B+Tree的理论容量可以这样粗算:根节点有1170个指针指向中间层节点,每个中间层节点又有1170个指针指向叶子节点,叶子节点总数约为1170乘1170,接近137万个;如果每个叶子节点保存16行记录,那么三层B+Tree就能覆盖约两千万行数据。这意味着一次主键点查最多只需要两到三次磁盘IO,效率远高于二叉搜索树。

InnoDB 中的 B+Tree 索引:聚簇与二级索引

在InnoDB存储引擎里,表本身就是按照主键组织成的一棵B+Tree,这棵树的叶子节点保存的是完整的行记录,所以主键索引也被称为聚簇索引。如果建表时没有显式指定主键,InnoDB会先尝试使用第一个非空唯一索引作为聚簇索引;如果连这样的索引都没有,它会生成一个隐藏的6字节rowid。因此,主键不宜使用过长的字符串,因为主键值会被二级索引引用,主键太大意味着所有二级索引都会膨胀。

二级索引和聚簇索引有很大区别。二级索引的叶子节点不再保存完整行数据,而是保存索引列的值和对应的主键值。当查询通过二级索引找到记录后,还需要根据主键值回到聚簇索引中查找完整数据,这个过程就是常说的回表。下面这个建表语句中,id上会自动创建聚簇索引,而idx_name_age就是一个联合二级索引。

CREATE TABLE user (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  age INT,
  KEY idx_name_age (name, age)
) ENGINE=InnoDB;

联合索引的排序遵循最左前缀原则。对于idx_name_age来说,数据先按照name排序,name相同的记录再按照age排序。因此,只查询name可以使用该索引,同时查询name和age也可以使用该索引,但只查询age就无法高效利用这个索引,因为age在整个索引中并不是全局有序的。范围查询同样会截断后续列的使用,比如name = '张三' AND age > 20能走索引定位到张三的起始位置,但age部分只能做过滤,无法继续利用索引的有序性。

从查询计划看 B+Tree 的查找过程

一次使用B+Tree索引的点查,通常从根节点开始,在节点内部通过二分查找或页目录定位目标键值所在的分支,然后逐层向下,直到叶子节点。对于主键等值查询,InnoDB可以直接在聚簇索引的叶子节点拿到整行数据。对于二级索引等值查询,则需要先定位二级索引叶子节点拿到主键,再回到聚簇索引查找,这个过程在查询计划中可能出现Using index condition或Using where等提示。

范围查询的路径稍有不同。例如WHERE name = '张三' AND age > 20会先通过name等值条件定位到叶子节点中第一个符合条件的记录,然后利用叶子节点之间的双向链表向右扫描,直到name不再等于张三或者扫描完整个范围。如果满足条件的记录很多,回表操作就会非常频繁,优化器可能因此放弃索引而选择全表扫描。可以通过EXPLAIN观察执行计划。

EXPLAIN SELECT * FROM user WHERE name = '张三' AND age > 20;

如果查询只需要name和age两列,而这两列又都在idx_name_age索引里,那么二级索引的叶子节点已经包含了查询需要的全部数据,不需要再回表。这种情况在查询计划中会显示为Using index,也就是覆盖索引。覆盖索引能明显减少磁盘IO,尤其是在大表分页或高频查询场景下,合理设计联合索引的列顺序可以显著提升性能。

使用 B+Tree 索引时容易踩的坑

索引列一旦被函数或表达式包裹,B+Tree就无法利用键值的有序性进行定位。例如WHERE YEAR(create_time) = 2023,即使create_time上有索引,也大概率不会走索引,因为YEAR(create_time)的结果无法直接和索引键值比较。更合理的写法是改成范围条件,如create_time >= '2023-01-01' AND create_time < '2024-01-01'。类似的隐式类型转换也要注意,字符串列传入数字参数可能会触发类型转换,导致索引部分失效。

联合索引的顺序也是一个常见问题。比如查询条件只有age,但建立的是idx_name_age,那么这个索引基本帮不上忙。还有前导模糊查询name LIKE '%张三',因为键值比较时无法确定起始范围,通常只能扫描整棵索引。负向条件如NOT IN、!=也可能让优化器选择全表扫描。区分度低的列即使建了索引,优化器也可能认为回表代价过大而放弃使用。

理解B+Tree索引归根结底是要理解它的排序和存储方式。索引不是建得越多越好,每个索引都会占用额外空间,并在插入、更新、删除时带来维护成本。设计索引时优先考虑区分度高、经常出现在查询条件中的列,并尽量让查询走覆盖索引,才能发挥B+Tree在磁盘IO和范围查询上的优势。

MySQL索引B+Tree聚簇索引修改时间:2026-09-22 10:39:51

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0922/60443.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。