Buffer Pool
2025/12/13大约 4 分钟约 1242 字
1. 什么是 Buffer Pool?
Buffer Pool (缓冲池) 是 InnoDB 存储引擎在内存中预留的一块区域,用于缓存磁盘中的数据页。
- 核心作用:数据库的增删改查(CRUD)操作都优先在 Buffer Pool 中进行,从而减少慢速的磁盘 I/O。
- 大小配置:由
innodb_buffer_pool_size控制,生产环境通常建议设置为物理内存的 60%-80%。
2. 内部结构
Buffer Pool 以 页 (Page) 为单位进行管理(默认 16KB)。主要包含以下几类数据:
- 数据页 (Data Pages):存储表中的实际数据。
- 索引页 (Index Pages):存储 B+ 树索引节点。
- Undo 页 (Undo Pages):存储事务回滚和 MVCC 所需的旧版本数据。
- Change Buffer:缓存非唯一索引的写入操作。
- 自适应哈希索引 (AHI):InnoDB 自动建立的内存索引。
- 锁信息:行锁、表锁等信息。
每个缓存页都会对应一个 控制块 (Control Block),记录该页的元数据(如页号、内存地址、所属表空间等),它是管理 Buffer Pool 的核心。
3. 三大链表管理
为了高效管理这些缓存页,InnoDB 维护了三个关键链表:
| 链表 | 名称 | 作用 |
|---|---|---|
| Free List | 空闲链表 | 记录所有空闲可用的页。当需要从磁盘读取新页到内存时,优先从这里申请。 |
| Flush List | 脏页链表 | 记录所有被修改过但未刷盘的页(脏页)。后台线程会按照修改时间顺序,将脏页刷新到磁盘。 |
| LRU List | 最近最少使用链表 | 记录所有正在使用的页。当 Free List 为空时,通过 LRU 算法淘汰最久未使用的页来腾出空间。 |
脏页 (Dirty Page):内存中被修改过,但磁盘上还是旧数据的页。
4. 改进的 LRU 算法 🔥
如果直接使用传统的 LRU(末尾淘汰)算法,MySQL 会面临两个严重问题:
- 预读失效:MySQL 的预读机制会加载相邻的页,如果这些页一直不被访问,会挤占热数据空间。
- Buffer Pool Pollution (缓冲池污染):全表扫描(如
SELECT *)会一次性加载大量数据页,虽然只访问一次,但会将真正的热点数据全部挤出 Buffer Pool,导致性能急剧下降。
优化策略:冷热分离
InnoDB 将 LRU 链表按比例(默认 5:3)分为两部分:
- New Generation (Young 区域):头部约 5/8,存放真正的热数据。
- Old Generation (Old 区域):尾部约 3/8,存放刚读入的数据。
晋升规则
- 新页入场:从磁盘新读入的页,默认插入到 Old 区域的头部(而不是整个 LRU 的头部)。
- 老页晋升:只有当页在 Old 区域停留时间超过 1 秒(
innodb_old_blocks_time)且再次被访问时,才会被移入 Young 区域的头部。 - 淘汰:当需要腾空间时,优先淘汰 Old 区域尾部 的页。
效果:全表扫描的数据虽然多,但只会待在 Old 区域并被快速淘汰,不会污染 Young 区域的热点数据。
5. Change Buffer (写缓冲)
Change Buffer 是 Buffer Pool 中的一块特殊区域(MySQL 5.5 之前叫 Insert Buffer),用于优化非唯一普通索引的写入性能。
工作原理
- 当需要更新一个数据页时,如果该页不在 Buffer Pool 中。
- 对于唯一索引:必须读入磁盘页以检查唯一性(无法优化)。
- 对于普通索引:直接将修改操作记录在 Change Buffer 中,不立即进行磁盘读。
- Merge:当下次查询需要访问该页(读入内存)时,或者后台线程空闲时,将 Change Buffer 中的修改合并到数据页中。
优势
大大减少了写操作导致的随机读磁盘 I/O。
6. 刷盘机制
脏页的刷盘是异步进行的,主要由后台线程(Page Cleaner Thread)负责。触发刷盘的 4 种主要场景:
Redo Log 满了 (Adaptive Flushing)
- 当 redo log 的写入位置(write_pos)快追上检查点(checkpoint)时,系统必须强制刷一部分脏页到磁盘,以推进 checkpoint,腾出 redo log 空间。
- 这种情况应尽量避免,因为会导致系统阻塞。
Buffer Pool 空间不足
- 当需要读入新页但 Free List 为空时,需要淘汰 LRU 链表尾部的数据页。
- 如果被淘汰的是脏页,则必须先将其刷盘才能释放内存。
系统空闲时
- MySQL 认为系统处于空闲状态时,后台线程会定期将一小部分脏页刷入磁盘,降低系统负载高峰时的压力。
MySQL 正常关闭
- 在正常关闭服务(Shutdown)时,MySQL 会将内存中所有的脏页都刷入磁盘,确保下次重启时不需要进行耗时的崩溃恢复。
- (受
innodb_fast_shutdown参数控制,默认为 1,即执行刷盘)。
