Buffer Pool + 页结构(理解为什么随机查会慢)

🚀 一句话先打基础

InnoDB 的最小读写单位是“页(Page)”,默认大小 16KB。
Buffer Pool 就是这些“页”的内存缓存池。
查一次数据,就是从磁盘把一个 16KB 的页搬到内存。


📦 1. 什么是页(Page)?

想象你有一本书,每一页 16KB。

你查一本书的某一段内容,肯定不会“翻到单独一个字”,而是整页整页地看。

MySQL 也是这么干的:

  • 数据是按照 存储的
  • 查询必须把 整页 读到内存
  • 页里包含多行记录(几十~几百行,和你的行宽有关)

👉 所以 “查一行数据” = 读一个 16KB 页

不管你只要其中的一条还是全部,它都是整页。


🧊 2. 什么是 Buffer Pool?

Buffer Pool = 页的高速缓存池。

你把它想成一个堆满“热数据页”的仓库:

  • 查询命中 Buffer Pool → 秒回
  • 查询没命中 → 需要从磁盘读页 → 多一次磁盘 I/O

而磁盘 I/O 就是数据库性能最大杀手。

读取一个页(随机 I/O):

  • SSD:0.1ms ~ 0.5ms
  • 内存:几十纳秒

差了 约 2 万倍

所以 Buffer Pool 命中率 对性能影响极大。


🧱 3. 页结构长啥样?

一个页(16KB)里大致包含:

+--------------------------------+
| Page Header       (头部信息) |
+--------------------------------+
| Infimum/Supremum  (虚拟行)   |
+--------------------------------+
| User Records      (真正的数据)|
+--------------------------------+
| Free Space        (空闲空间) |
+--------------------------------+
| Page Directory    (槽)        |
+--------------------------------+
| Trailer           (校验)      |
+--------------------------------+

核心是:

💥 页里不是乱存的

数据按主键排序,用链表串起来(物理有序不存在,只是页内有序)。

💥 页目录(slots)让查找更快

类似“页内索引”,让查某行不需要线性扫描。

这也解释了:

  • 为什么主键自增插入快
  • 为什么随机主键插入会分裂页、很慢

🧭 4. 为什么随机查询会慢?(核心来了)

我们以 主键查询 id=1 vs 随机条件查询 name='Tom' 对比:


场景 A:主键查询(顺序 I/O)

主键索引(聚簇索引)是一棵 B+Tree。

查 id=100:

  1. B+Tree 找到对应叶子节点页
  2. 叶子节点里的那个“页”读出来
  3. 返回结果

只需要查一次叶子节点 + 根节点缓存通常在 Buffer Pool
→ I/O 次数极少

主键查找性能非常稳定。


场景 B:随机条件(非主键)查询(随机 I/O)

比如:

SELECT * FROM user WHERE name = 'Tom';

流程变成:

  1. 先查 name 的二级索引(非聚簇索引)
  2. 找到记录对应的主键值(例如 id = 100)
  3. 再去聚簇索引树里根据主键“回表”查数据
  4. 这意味着至少两个页要被读入内存

如果这两个页都不在 Buffer Pool?
两次磁盘随机 I/O

再想象有成千上万条,需要回表成千上万次……

随机查询为什么慢:
因为会导致大量“回表”,而每次回表都是一次随机 I/O。

百万级表随机查询 → I/O 炸裂。


💣 进一步:为什么范围查询快?

因为 InnoDB 页之间通过双向链表连接。

查主键范围:

WHERE id BETWEEN 1 AND 100000

只需要:

  1. 找到第一页
  2. 顺着链表向后遍历页数据
    (顺序 I/O,磁盘一次读多个连续页 → 快)

而随机条件查 name='Tom'
→ 分布到不同页 → 全是随机 I/O → 颠簸死


🎯 随机查询慢的终极原因总结

✔ 页是 16KB
✔ 查询必须整页加载
✔ 随机数据可能分散在大量页里
✔ 每次回表都是一次磁盘随机 I/O
✔ Buffer Pool 容量有限,不可能所有页都常驻内存
✔ 随机 I/O 是数据库最贵的操作,慢几十倍到几万倍

所以:
随机查询 = 大量随机读页 = 性能自杀。


🔧 工程师能用的优化方法(你马上能用)

  1. 尽量让查询变成主键查询
  2. 尽量用覆盖索引(避免回表)
  3. 让热点数据留在 Buffer Pool
  4. 页分裂要尽量少(使用自增主键)
  5. 表越“大页”,Buffer Pool 越“紧张”
  6. 删掉无用索引,避免写放大

posted @ 2025-12-06 14:18  中登程序猿  阅读(0)  评论(0)    收藏  举报