MySQL 索引底层 B+ 树原理
聊 MySQL 索引不讲 B 树那就是在耍流氓。大家好我是乱码字符。今天咱们深入聊聊 MySQL 索引的底层数据结构——B 树。这篇文章能让你彻底搞明白为什么有时候明明加了索引查询却还是慢成狗。先说说为什么要用树结构想象一下如果数据存在数组里你要查一条id 10086的记录最坏情况得遍历整个数组时间复杂度 O(n)。数据少还行一旦数据量级到了千万、亿这个级别查询效率就炸了。所以我们需要一种查找效率高的数据结构。哈希表那玩意儿只能做等值查询范围查询比如查id 100就歇菜了。树结构天然支持高效的范围查询这就是 MySQL 选择 B 树的主要原因。B 树长啥样先上一张经典的 B 树结构图建议收藏[50 | 100] / | \ [20|30] [60|80] [120|150] / | \ / | \ / | \ ... ... ... ... ... ... ... 简单说B 树是一种**多叉平衡树**有多牛呢看几个关键特性 1. **只有叶子节点存数据**——非叶子节点只存索引键值叶子节点才是真正存数据的地方 2. 2. **叶子节点用指针串起来**——这也就是为什么 B 树特别适合范围查询 3. 3. **绝对平衡**——所有叶子节点都在同一层查询性能极其稳定 ## B 树 vs B 树有啥区别 面试官最喜欢问这个记住了 | 特性 | B 树 | B 树 | |------|------|-------| | 数据存储 | 非叶子节点也存数据 | 只有叶子节点存数据 | | 范围查询 | 需要遍历整棵树 | 叶子节点用指针串起直接遍历 | | 查询稳定性 | 最差 O(log n) | 稳定 O(log n)因为只有叶子节点存数据 | | IO 次数 | 更多非叶子节点也读磁盘 | 更少矮胖结构 | ## MySQL 中 B 树是怎么存的 在 InnoDB 存储引擎中**主键索引就是一颗 B 树**。 假设我们有一张用户表 sql CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL, age int DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB; 当你执行 SELECT * FROM user WHERE id 10 时MySQL 干的事儿是这样的 1. 先从**主键索引树**的根节点开始 2. 2. 根节点里存了页目录根据 id10 找到对应的指针 3. 3. 往下走最终定位到叶子节点 4. 4. 叶子节点里存的才是完整的行数据 整个过程只需要 **3-4 次磁盘 IO**取决于树的高度非常快。 ### 等等主键索引叶子节点存啥 InnoDB 的主键索引叶子节点存的是**完整的行数据**。这就是所谓的**聚簇索引**Clustered Index。 那如果我建的是普通索引非主键索引呢 sql CREATE INDEX idx_age ON user(age);普通索引的叶子节点存的不是完整数据而是主键值 索引列的值。查的时候先通过普通索引找到主键再去主键索引树查一遍——这叫回表。一个页能存多少数据InnoDB 默认页大小是16KB。假设主键是 bigint8字节加上一些指针开销一行数据大概占 1KB那么一个页能存16条左右的数据。一个 B 树根节点能存 1000 多条索引每个索引往下能分出 1000 多个子节点……算下来高度为 2 的 B 树1000 × 16 1.6万条数据高度为 3 的 B 树1000 × 1000 × 16 1600万条数据高度为 4 的 B 树能存16亿条数据所以绝大多数情况下B 树的高度都是2-3 层查询只需要 2-4 次磁盘 IO。什么时候索引会失效既然索引这么好使为啥有时候加了索引还是慢常见原因1. 索引列用了函数或运算-- 索引失效SELECT*FROMuserWHEREYEAR(created_at)2024;-- 应该改成范围查询SELECT*FROMuserWHEREcreated_at2024-01-01ANDcreated_at2025-01-01;2. 字符串不加引号-- 索引失效隐式类型转换SELECT*FROMuserWHEREphone13800138000;-- 正确写法SELECT*FROMuserWHEREphone13800138000;3. 最左前缀原则-- 建立了索引 (name, age, city)-- 可以用索引的情况WHEREnameTomWHEREnameTomANDage25-- 不能用索引的情况WHEREage25WHEREcityBeijing4. 模糊查询%开头的最左前缀原则-- 索引失效SELECT*FROMuserWHEREnameLIKE%om;-- 可以用索引SELECT*FROMuserWHEREnameLIKETom%;实战看看你的索引长啥样用EXPLAIN看看查询有没有走索引EXPLAINSELECT*FROMuserWHEREid10;输出里的type列很重要type 值含义const常量查询用了主键索引或唯一索引ref用了普通索引查找多条range范围查询index全索引扫描ALL全表扫描最糟糕总结一下B 树是 MySQL 索引的底层结构只有叶子节点存数据用指针串起来适合范围查询主键索引是聚簇索引叶子节点存完整行数据普通索引是非聚簇索引叶子节点存主键值需要回表注意索引失效的几种情况尤其是函数、隐式转换、最左前缀用 EXPLAIN 分析查询确保走了索引而不是全表扫描好了B 树原理就聊到这儿。如果还想看更多 MySQL 硬核原理点个关注咱们下期见。往期热门[MySQL 事务隔离级别详解][MySQL 执行计划 EXPLAIN 详解][MySQL 慢查询优化实战]