MySql - InnoDB 之 Buffer Pool | 重要数据结构
Posted Zohar Yip
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySql - InnoDB 之 Buffer Pool | 重要数据结构相关的知识,希望对你有一定的参考价值。
Buffer PoolInnoDBmysqlDatabase
对于基于磁盘的存储数据库系统,最重要的目的就是高效地存取数据。但由于 CPU 和磁盘速度之间存在难以逾越的鸿沟,为了弥补二者之间的速度差异,必须使用缓冲池技术来加速数据的存取。因此,缓冲池 是 InnoDB 最为重要的部分。
也因为引入这一中间层,使得 InnoDB 对数据库内存的管理变得相对更为复杂。缓冲池主要包括以下特性:LRU List、Free List、Fulsh List、Fulsh 策略、Double write buffer、预读预写、预热、动态扩展、压缩页内存管理、并发控制、多线程工作等。
重要对象基本概念
Buffer Pool Instance
InnoDB 1.0.x 版本开始,缓冲池可以分为多个缓冲池实例,每个页面根据哈希值平均分配到不同的缓冲池实例中去。每个实例资源独立,拥有自己的锁、信号量、物理块、逻辑链表,页哈希表等,这样就可以通过减少缓冲池内部的资源竞争以提高引擎整体的性能。相关参数为:
-
innodb_buffer_pool_instances
:记为 n,缓冲池实例数量。 -
innodb_buffer_pool_size
:记为 m,所有缓冲池总大小。
buf_pool_t
buf_pool_t
数据结构,称为缓冲池控制体。缓冲池控制体用于存储该缓冲池实例的控制信息,如缓冲池实例的锁、实例编号、页哈希表等等信息,还存储了各种逻辑链表的链表根节点。Zip Free 这个二维数组也在其中。部分代码为:
struct buf_pool_t {
...
ulint instance_no; // 缓冲池实例编号
ulint curr_pool_size; // 缓冲池实例大小
buf_chunk_t *chunks; // 缓冲池实例的物理块列表
hash_table_t *page_hash; // 页哈希表
UT_LIST_BASE_NODE_T(buf_page_t) free; // 空闲链表
UT_LIST_BASE_NODE_T(buf_page_t) LRU; // LRU 链表
UT_LIST_BASE_NODE_T(buf_page_t) flush_list; // Flush 链表
BufListMutex free_list_mutex; // 空闲链表的互斥锁
BufListMutex LRU_list_mutex; // LRU 链表的互斥锁
BufListMutex flush_state_mutex; // Flush 链表的互斥锁
...
}
Page
数据页 是 InnoDB 中最小的数据管理单位,默认为 16KB,InnoDB 1.2.x 版本开始可以修改页大小为 4K、8K、16K,引擎首次启动之后便无法再更改页大小。
如果对表进行压缩,则对应的数据页称为压缩页,压缩页大小在建表时指定,支持 1K、2K、4K、8K、16K,压缩为 16K 虽然没有节约空间但对 blob、varchar、text 类型有一定好处。从压缩页中读取数据需要先解压形成解压页再读取,解压页与数据库默认页大小相同。如果压缩页大小指定为 4K 但数据页无法压缩到 4K 以下,则会对数据页进行一次页分裂操作。
正常情况下,缓冲池会同时缓存压缩页及其解压页,当空闲列表不够用时会根据系统是实时负载决定淘汰策略:如果系统瓶颈在 IO 上就淘汰解压页,否则两者都淘汰。
脏页 指缓冲池中数据被修改了但是还没落盘的数据页。无论普通数据页还是压缩页只要发生数据更新都可以称为脏页,脏页的会被链接到 Flush 链表中。每隔一段时间或者系统空闲时会有部分脏页被更新到磁盘中,在脏页被剔除出缓冲池的时候必定会进行落盘操作。
每个数据页都会有与之对应的数据页控制体,用于存储数据页相关的各项数据和指向数据页的指针,数据页控制体由两种数据结构一起组成,分别为 buf_page_t
和 buf_block_t
。
buf_block_t
struct buf_block_t {
buf_page_t page; // 另一个控制块 buf_page_t 的指针,必须作为第一个数据成员
byte *frame; // 数据页指针,指向真正的的数据页
BPageMutex mutex; // 页锁
...
}
数据页的控制体之一,描述少量数据页的信息。第一个数据成员就是另一个数据页控制块指针,必须作为第一个数据成员以随时转换成另一个数据页控制块。第二个数据成员 frame
是指向所属数据页的指针。
buf_page_t
struct buf_page_t {
...
page_id_t id; // page id
page_size_t size; // page 大小
ib_uint32_t buf_fix_count; // 用于并发控制
buf_io_fix io_fix; // 用于并发控制
buf_page_state state; // 页状态
lsn_t newest_modification; // 最新 lsn,即最近修改的 lsn
lsn_t oldest_modification; // 最老 lsn,即第一条修改 lsn
...
}
buf_page_t
数据结构,称为数据页控制体。该控制体存储了绝大部分数据页信息,包括页 ID、页大小、页状态、最新 lsn、最老 lsn 以及压缩页的所有信息等。压缩页信息包括压缩页大小、压缩页指针。
Buffer Chunk
页是 InnoDB 内存最小的数据管理单位,但连续的内存存储单位是物理块。
buf_block_t
为元素的控制块数组,另一个是以数据页为元素的数据页数组,控制块数组占用 Chunk 前部分,数据页数组占用 Chunk 后部分。每个数据页必定存在与之对应的控制块,但控制块不一定有与之对应的数据页。数据块中几乎包含所有数据页,仅有
BUF_BLOCK_ZIP_PAGE
和
BUF_BLOCK_ZIP_DIRTY
类型数据页除外。数据页并不都是存储用户数据,控制信息、行锁、自适应哈希也存在于数据页中。
逻辑链表
buf_page_t
控制体,引入各类型逻辑链表使得数据页的管理更方便系统。
Free List
LRU List
LRU List 是缓冲池中最重要的数据结构,基本所有读入的数据页都缓冲于其上。LRU 链表顾名思义根据最近最少使用算法对节点进行淘汰,但在这里所使用的是优化后的 LRU 算法。
Flush List
缓冲池中所有脏页都会挂载在 Flush List 中,以等待数据落盘。LRU List 中的页被修改后也会被放入 Flush List 中,被修改过后的压缩页也会被放入 Flush List 中。在数据更改被刷入磁盘前,数据很有可能会被修改多次,在数据页控制体中记录了最新修改的 lsn(newset_modification
) 和最老修改的 lsn(newest_modification
)。进入 Flush list 的节点按照进入的顺序进行排序,最新加入的数据页放在链表头部。数据页在进入 Flush List 时对 Flush List 加锁以保证节点进入的顺序。刷数据时从链表尾开始写入。
Unzip LRU List
Zip Clean List
Zip Free
Mutex
buf_pool_t
中为几个逻辑链表维护了几个互斥锁,用来保护各链表的并发访问:
名称 |
类型 | 目标 |
---|---|---|
lru_list_mutex |
互斥量 |
LRU 列表 |
free_list_mutex |
互斥量 |
Free 列表 |
flush_list_mutex |
互斥量 | Flush 列表 |
hash_lock |
读写锁 | Page hash |
buffer block mutex |
互斥量 |
buf_block_t |
buf_fix_count |
信号量 | buf_page_t |
io_fix |
状态量 |
buf_page_t |
rw_lock(BPageMutex) |
读写锁 |
数据页 |
List Mutex
所有数据页都在空闲列表、LRU 列表和 Flush 列表上,因此必须先获取这几个列表的锁才能进行 IO 操作。
hash_lock
在 MySql 5.6 版本之前,对页哈希表的操作使用一个 Page Hash 级别的锁。而后优化为 slot 级别的 hash_lock,即页哈希表有多少个 slot 就有多少个 hash_lock,以尽量减少锁冲突。
在获取 hash_lock 并访问到数据页的后,就会直接放开 hash_lock。
page block mutex
buffer block mutex 是 buf_block_t 上的锁,用于保护 buf_page_t 上的 io_fix、state、buf_fix_count 等变量, 引入这个 mutex 是为了减少早期版本直接使用缓冲池级别锁的开销。
buf_fix_count 与 io_fix
io_fix 表示当前的 page frame 正在进行的 IO 操作状态,主要有 BUF_IO_READ、BUF_IO_WRITE、BUF_IO_PIN、BUF_IO_NONE。
buf_fix_count 表示当前这个控制块被引用了多少次, 每次访问一个 page 的时候, 都会对 buf_fix_count++,最后在 mtr:commit()
的最后资源释放阶段, 会对这个buf_fix_count--,进行资源的释放.
page frame rw_lock
在判断一个页能否被 flush 的时候,会先通过判断 io_fix 状态以减少直接获取 page frame rw_lock 的操作。
获取 hash_lock,获取 page block lock 之后释放 hash_lock。
判断并修改 io_fix 和 buf_fix_count,然后放开 page block lock。
获取 page frame rw_lock。
Page Hash 与 Zip Hash
space_id
和
page_id
来定位与读取数据。
Double Write Buffer
innodb_doublewrite_batch_size = 120
。
buf_dblwr_add_to_batch
表示写成功。不过此时后续对磁盘双写缓冲区的写请求依然会被阻塞,只有在确认异步操作都成功后才会清空系统表空间双写缓冲上的内容,后续请求才能被继续执行。这样做的目的是:如果在异步写回数据页的时候,系统断电发生了数据页半写,由于系统表空间中双写缓冲区的数据页是完整的,重新拷贝即可。异步 IO 请求完成后,会检查数据页的完整性以及完成 change buffer 相关操作,接着 IO helper 线程会调用
buf_flush_write_complete
把数据页从 Flush 列表中删除。
以上是关于MySql - InnoDB 之 Buffer Pool | 重要数据结构的主要内容,如果未能解决你的问题,请参考以下文章
mysql参数之innodb_buffer_pool_size大小设置