Buffer Pool 详解

如果把 MySQL(准确来说是 InnoDB) 比作一个大型图书馆:

  • 磁盘(Disk) = 仓库

  • Buffer Pool = 阅览室

  • CPU = 读书的人

数据库不会每次查询都跑到磁盘拿数据,而是会先把热点数据放到内存中,这块内存区域就是 Buffer Pool(缓冲池)


一、什么是 Buffer Pool

Buffer Pool 是 InnoDB 存储引擎用于:

  • 缓存数据页(Data Page)

  • 缓存索引页(Index Page)

  • 缓存 Undo Page

  • 缓存 Insert Buffer

  • 缓存 Adaptive Hash Index

的一块巨大的内存区域。

其核心目标:

减少磁盘 IO,提高数据库性能。


例如:

SELECT * FROM user WHERE id = 100;

第一次查询:

磁盘
读取数据页
Buffer Pool
返回结果

第二次查询:

Buffer Pool
直接返回

不需要再访问磁盘。


二、为什么需要 Buffer Pool

先看磁盘和内存的速度差异。

存储介质 延迟
CPU缓存 ns
内存 100ns
SSD 100μs
HDD 10ms

换算:

内存 ≈ 0.0001 ms

SSD ≈ 0.1 ms

HDD ≈ 10 ms

差距:

磁盘 > 内存
约1000~100000倍

如果每次 SQL 都访问磁盘:

select * from user where id=1;

性能会极差。

因此:

热点数据
Buffer Pool
减少IO
提升QPS

三、Buffer Pool 缓存的是什么

很多人以为缓存的是一条记录。

实际上:

Buffer Pool 缓存的是 Page(页)。


四、InnoDB 的页(Page)

InnoDB 最小管理单位:

Page

默认大小:

16KB

例如表:

user

数据:

id  name
1   Tom
2   Jack
3   Lucy
...

磁盘中存储:

Page1
Page2
Page3
...

结构:

Page1
 ├─ Row1
 ├─ Row2
 ├─ Row3
 └─ ...

当查询:

select * from user where id=1;

并不是读取这一行。

而是:

读取整个Page
16KB

加载到 Buffer Pool。

Disk
 └─ Page1


Buffer Pool
 └─ Page1

后续访问同页数据:

select * from user where id=2;
select * from user where id=3;

无需磁盘IO。


五、Buffer Pool 的内部结构

Buffer Pool 本质:

Buffer Pool
├─ Free List
├─ LRU List
├─ Flush List
└─ Hash Table

这是高频题。


六、Free List(空闲链表)

Buffer Pool 初始化时:

+-----+
| Page|
+-----+
| Page|
+-----+
| Page|
+-----+

这些内存块称:

Buffer Frame

结构:

Buffer Frame
└─ 存放一个16KB Page

未使用的 Frame:

Free List
Free List

Frame1
Frame2
Frame3
Frame4

当需要读取数据页:

从Free List取一个Frame

放入:

Disk Page
Frame

七、LRU List(最重要)

Buffer Pool 不可能无限大。

例如:

innodb_buffer_pool_size=8G

满了怎么办?

需要淘汰页面。


普通LRU

理论上:

最近访问
放头部

长时间没访问
尾部淘汰
Head

A→B→C→D→E

Tail

淘汰:

E

问题

例如:

select * from order;

全表扫描:

Page1
Page2
Page3
...
Page100000

这些页都只访问一次。

如果全部放到 LRU 头部:

热点页被挤出去

缓存命中率暴跌。


八、InnoDB 改进版 LRU

MySQL 没直接使用传统 LRU。

而是:

Young Area
Old Area

结构:

Head

Young
├─────────────
Old

Tail

默认比例:

5 : 3

约:

63%
37%

新页进入 Old

很多人以为:

新页 → Young

实际上:

新页 → Old头部
Young

---------

Old
 新页

原因:

防止全表扫描污染缓存。


第二次访问

如果:

第一次访问

进入:

Old

之后再次访问:

Old → Young

说明:

是真热点数据

因此:

一次访问
Old

多次访问
Young

提高缓存命中率。


九、Flush List(刷盘链表)

当执行:

update user
set name='Tom'
where id=1;

修改流程:

磁盘Page
Buffer Pool Page
修改内存

此时:

内存 ≠ 磁盘

产生:

Dirty Page(脏页)

例如:

Page1
name=Tom

内存:

Page1
name=Jerry

磁盘:

Page1
name=Tom

不一致。


所有脏页:

Flush List

维护。

Flush List

Page3
Page8
Page10

后台线程:

Page Cleaner

负责:

脏页刷盘

十、Hash Table(页定位)

假设:

Buffer Pool
8GB

里面:

50万Page

查询一个 Page:

遍历?

显然不现实。

所以有:

Page Number
Hash
Buffer Frame

实现:

O(1)

快速定位。


十一、Buffer Pool 与 Redo Log 的关系

这是最核心的设计之一。


假设:

update user
set age=18
where id=1;

如果:

修改内存
立刻刷盘

问题:

随机IO太多

性能差。


InnoDB 采用:

WAL(Write Ahead Logging)

先写日志。


流程:

1 修改Buffer Pool

2 记录Redo Log

3 提交事务

4 后台刷脏页
UPDATE


Buffer Pool
(脏页)


Redo Log
(顺序写)


Commit


后台刷盘

优势:

随机写

顺序写日志


性能暴增

十二、Buffer Pool 命中率

查看:

SHOW ENGINE INNODB STATUS;

或者:

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';

重点:

Innodb_buffer_pool_read_requests

逻辑读次数。

Innodb_buffer_pool_reads

真正磁盘读次数。


计算:

Hit Rate

=
1 - reads/read_requests

例如:

read_requests = 1000000

reads = 1000

命中率:

99.9%

优秀。


十三、Buffer Pool 为什么通常配置为物理内存的 60%~80%

例如:

服务器
64GB RAM

配置:

innodb_buffer_pool_size=48G

原因:

系统还需要:

  • OS Page Cache

  • 连接线程

  • 排序缓冲区

  • Join Buffer

  • Binlog Cache

  • Redo Log Buffer

不能全部给 Buffer Pool。


十四、Buffer Pool 完整工作流程

            SQL
       查找数据页
      Buffer Pool ?
       │        │
       │命中     │未命中
       ▼        ▼
    直接返回   磁盘读取
            Free List
             LRU List
              返回数据

------------------------------------------------

UPDATE
修改Buffer Pool页
变成Dirty Page
加入Flush List
写Redo Log
事务提交
后台线程刷盘

一句话总结:

Buffer Pool 是 InnoDB 最重要的内存组件,本质上是一个以 Page(默认16KB)为单位的缓存系统。它通过 Free List 管理空闲页、LRU 管理热点页、Flush List 管理脏页,并结合 Redo Log 的 WAL 机制实现“内存修改 + 日志持久化 + 异步刷盘”,从而将大量随机磁盘 IO 转换成内存访问和顺序日志写入,这是 MySQL 高性能的核心基础之一。