一文讲透mysql的索引树:从图书馆的故事开始

 

一、为什么需要索引?先讲个图书馆的故事

想象一个巨大的图书馆,里面有上千万本书,但所有书都是乱序堆在地上的。你要找一本编号为“9527”的书,唯一的办法就是从第一本开始,一本一本地翻看编号,直到找到为止。这可能需要几天时间。这就是数据库没有索引时的“全表扫描”,效率极低。

聪明的图书管理员想了个办法:建立一本“索引目录册”

  1. 第一版目录(简陋版):目录册上按顺序记录了每本书的编号和它的具体位置(如:A区-3排-2架-5层)。找书时,你先在目录册里快速翻到编号“9527”,然后直接去对应位置拿书。快多了!这就像在一个有序数组里用二分查找,但目录册本身太厚,翻起来也慢。

  2. 第二版目录(升级版)——这就是B+树:管理员把目录册升级成了一个多级的、树状的结构。

    • 顶层(根目录):只有一页纸,上面写着:“编号1-5000的书,去1号子目录查;编号5001-10000的书,去2号子目录查...”
    • 中层(子目录):你根据根目录指引,找到2号子目录。它上面写得更细:“编号5001-6000,去A书架;编号6001-7000,去B书架...”
    • 底层(书架本身):你最终来到B书架,这里的书严格按照编号顺序排列,并且书架之间用绳子连起来,方便按顺序浏览。你很快找到了编号“9527”的书。

这个“根目录-子目录-书架”的多级结构,就是B+树索引。 它让查找次数从千万级,降低到了仅仅3次(根→子→书架)。


二、索引树是怎么“长”出来的?用自增ID建表为例

现在,我们扮演数据库,亲自建一个“图书馆”。假设我们的“书架”(在MySQL里叫)很小,一个书架只能放4本书(实际能放很多)。

我们建一个用户表,主键ID是自增的(就像给每本新书一个按顺序递增的编号):

CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT, -- 自增编号
    name VARCHAR(100)
);

第1步:第一本书上架(id=1)
图书馆刚开业,我们申请了第一个书架(页10)。把书《id=1, name=张三》放进去。此时,这个书架既是“总目录”,也是“藏书架”。树只有1层高。

[书架10:总目录 & 藏书架]
| id=1 (张三) | (空) | (空) | (空) |

第2步:顺序上架新书(id=2, 3, 4)
新书源源不断,编号顺序增加。我们都把它们按顺序放到同一个书架10的后面空位。

[书架10:总目录 & 藏书架]
| id=1 (张三) | id=2 (李四) | id=3 (王五) | id=4 (赵六) | <- 书架满了!

第3步:关键!插入id=5,书架满了怎么办?——分裂与重组
这是最容易误解的地方。当要放入《id=5, name=小明》时,书架10满了。

  • 如果新书编号是随机的(比如id=500),它可能应该插在书架中间。这时,管理员不得不把书架10的书搬出一半,放到一个新书架20里,然后更新总目录,这个过程空间利用率会接近50%
  • 但我们是自增ID! 新书id=5永远是当前最大编号。聪明的管理员(InnoDB存储引擎)会做一个优化:他几乎不动书架10的书,而是直接申请一个新书架20,把id=5这本新书放进去。然后,他新建一个真正的总目录页(页30),用来记录:“书架10里的书,编号都小于5;书架20里的书,编号从5开始”。
           [总目录页30]
        “找<5的去10,找>=5的去20”
           /            \
          /              \
[书架10-藏书架]      [书架20-藏书架]
|1|2|3|4|           |5|(空)|(空)|(空)|
(几乎满的)        (新的,准备放6,7,8...)

看,自增ID插入,几乎不会浪费50%空间,老书架(页10)依然是满的。空间浪费严重的“对半分裂”,更多发生在随机插入的时候。

第4步:树继续长高
随着id=6,7,8...的书进来,它们会顺序填满书架20。当书架20也满时,再次用优化方式分裂出书架40。当“总目录页30”的格子也写满时,总目录自己也会分裂,产生更高一级的“超级总目录”,树就从2层长高到3层。最终,所有“书”(数据行)都整齐地放在最底层的“藏书架”(叶子节点)上,并且书架之间用“绳子”(双向链表)连起来,方便按顺序找书。而所有上层目录(非叶子节点)只存编号和指向下层书架的指针。


三、为什么一个“书架”要设计成16KB这么大?

为什么不是4KB,或者64KB?这是工程师在硬件与效率间的精妙权衡。

  1. 磁盘的“最小读写单位”是块(通常4KB)。一次读16KB(4个连续块),远比读4次4KB快。如果设置太大(如64KB),单次读取得太久,而且可能读了很多没用的数据。

  2. 内存的“缓存池”有限。数据库会把常用书架(页)缓存在内存里。16KB的大小,能让内存同时缓存足够多的页,同时每页携带的数据量也足够有用。

  3. 最核心的原因:控制“树”的高度
    我们来算一笔账:

    • 一个16KB的“目录页”(非叶子节点),大约可以放下 1000多条 目录条目(比如主键ID+书架指针)。
    • 一个16KB的“藏书架”(叶子节点),假设每本书(每行数据)占1KB,那就能放 16本 书。
    • 那么:
      • 2层高的B+树:能存 1000 * 16 = 1.6万 本书。
      • 3层高的B+树:能存 1000 * 1000 * 16 = 1600万 本书。
      • 4层高的B+树:能存 1000 * 1000 * 1000 * 16 = 160亿 本书。

    看到了吗?仅仅3到4层的B+树,就能装下千万甚至百亿级的数据! 找任何一本书,最多只需要找3-4个书架(3-4次磁盘I/O)。16KB,正是在单次I/O数据量和树的高度之间找到的“黄金平衡点”。


四、终极面试题:为什么不用更简单的二叉树/红黑树?

二叉树(包括红黑树)在内存里是王者,但面对磁盘,它有致命伤。

回到图书馆比喻:

  • 二叉树/红黑树:像一个非常“高瘦”的决策树。每个决策点(节点)只分两支(左和右)。要找第1000万本书,你需要从顶层开始,做出大约24次(log₂(1000万))选择,也就是访问24个分散在不同位置的“决策点”。每次访问都可能是一次慢速的磁盘读取,总共24次I/O,慢到无法接受。

  • B+树:像一个“矮胖”的多路决策树。每个决策点(页)有几百上千个分支。要找第1000万本书,你只需要做3-4次选择(访问3-4个页),就能定位到书架。3-4次 vs 24次 I/O,这就是数量级的性能差距。

此外,B+树还有两大独家法宝:

  1. 范围查询快:因为所有“书”都在最底层的“书架”上,并且书架用绳子连着。要找编号1000到2000的所有书,你只需要找到编号1000的书架,然后顺着绳子往后扫就行。二叉树则需要不停地上下遍历,极其低效。
  2. 查询稳定:在B+树里,找任何一本书,走的“层数”都是一样的(树的高度)。而在二叉树里,找顶层的书和找最底层的书,速度天差地别。

总结一下:

  • 二叉树/红黑树“高瘦”,适合内存小数据量,但磁盘I/O次数多,范围查询慢。
  • B+树“矮胖”,专为磁盘设计。它利用磁盘块读取的特性,用最少的I/O次数定位数据,并且叶子节点链表让范围查询和顺序扫描快如闪电。

所以,MySQL选择B+树,不是因为它理论上的时间复杂度更优(都是O(log N)),而是因为它log的底数巨大,且其物理结构完美契合了磁盘的工作原理和数据库的查询模式。这就是它成为数据库索引标准答案的根本原因。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值