buffer pool 中的数据结构
1 buffer pool 初始化
buffer pool存在于内存中,是innoDB的核心组件,增删改操作都是作用在buffer pool上。
buffer pool的大小可以通过 innodb_buffer_pool_size(核心参数) 配置。
buffer pool缓存的是一个个数据页,数据页里存的是一行行数据。
每个缓存页对应一个元数据块,存储缓存页的地址,数据页的表空间、页号信息等。
数据库启动时,根据 innodb_buffer_pool_size,加上元数据的内存,向 os 申请一块内存。
申请了内存后,按照数据页大小和元数据大小,分出一个个缓存页及相应的元数据。
2 free链表和flush链表
这两个链表是由元数据块的节点构成双向链表,每个节点包含pre和next指针。
free链表用于组织空闲缓存页;flush链表用于组织脏页(被修改过的缓存页)。
基础节点存储链表的头尾节点信息和长度,元数据块位于buffer pool中。
元数据块存储缓存页所存的数据页的表空间,页号,缓存页的地址等。

3 如何判断数据页是否在缓存中
写sql只关注表+行,mysql内部对应表空间+数据页。
有一个哈希表维护了表空间+数据页号到缓存页地址的映射,如果存在,则说明被缓存。
4 缓存页的LRU淘汰机制
维护一个LRU链表,最近被访问的被放到链表头部。
LRU 淘汰存在的问题
mysql 预读机制会加载一些并不需要的数据页,这些数据页放到LRU链表头部。
这样会导致尾部本来被频繁访问的数据页被刷出缓存。
innodb 预读机制的触发时机
顺序访问了一个区的若干数据页,innodb_read_ahead_threshold
或者缓存了一个区的连续数据页,innodb_random_read_ahead
此外,全表扫描也会导致链表尾部频繁访问的数据页可能被淘汰。
5 表、行&表空间、数据页
前者是逻辑上的概念;后者是物理上的概念。
正如redo、undo是逻辑上的概念;binlog是物理概念
LRU 链表优化
为了解决预读带来的问题,引入类似于two list的策略 midpoint insertion strategy。
innodb_old_blocks_pct 代表old list的比例,默认值是37,代表37%是冷数据。
new list都是代表最近被访问的数据,也即热点数据。
**

innodb_old_blocks_time默认为1000 ms,经过1s,才从old list迁移到new list。
如果time设置太长,或者全表扫描,或者pct设置太小,容易导致old list缓存页被刷出。
通过冷热数据分离,可以解决预读机制和全表扫描带来的问题。
因为预加载和全表扫描的数据页一般1s内被访问,因此不会进入new list。
淘汰缓存页只需要把old list尾部的缓存页刷到磁盘即可。
LRU 改造:针对 new list 中的热点数据,可以采取前3/4被访问的话,不修改数据;
后1/4缓存页被访问,就将其移动到链表头部。
缓存预加载
对于业务数据,基于冷热分离的思想,可以启动一个定时 job,把数据加载到缓存里。
buffer pool 运行机制
1 加载缓存页
free 链表移除该缓存页,lru链表可能将冷数据页移动到链表头部
2 查询缓存页
将缓存页放到链表头部
3 修改缓存页
flush 链表记录脏页,lru链表可能将冷数据页移动到链表头部
4 定时刷缓存
光把lru链表中的冷数据刷入磁盘,是不够的。
后台线程在mysql不忙时,会把flush链表中的数据都刷入磁盘。
同时,这些缓存页也会从flush和lru链表移除。
5 无空闲页
如果 free 链表无空闲页,flush 链表有很多脏页,就从lru链表尾部刷缓存页到磁盘,flush链表删除缓存页,free链表增加缓存页。
buffer pool 与并发
mysql可能同时接收到多个请求,多个线程同时操作 buffer pool里的缓存页和链表。
比如加载缓存页,涉及到IO,会导致其他线程等待,实际可以配置多个buffer pool。
buffer pool 的 chunk 机制
每个buffer pool由若干chunk组成,共用free链表,flush链表,lru链表。
支持运行时动态调整buffer pool的大小。
