本文针对的是
mysql innodb特性,结合源码
先明白几个点:
- 真实物理数据存在磁盘中。不管是固态还是机械硬盘。
sql获取的是每一行数据,但是server从磁盘中获取单位是page。一个page包含多个record。- 增删改查,
server操作的地方都在内存
上述想说明什么?数据在磁盘,但是操作数据在内存,同时获取数据到内存是以 page 为单位。
从日常业务中看,redis 是解决服务获取数据库数据 I/O 延迟消耗,从而把数据从 DB -> redis。然后我们看回到这里,那么 innodb 如果要缓存数据,也需要一个 缓存池,用于管理相应的 page,提高数据库的效率。
这个就是 buffer pool,当然也因为引入这个中间层,管理也变得相当复杂:
- 内存管理【预热,加入,淘汰】
- 并发访问
- 一致性问题【修改了数据(内存),磁盘数据一致性怎么处理】
基础知识
这个是 buffer pool 的结构体定义。从这里可以看到 pool 的全貌:
struct buf_pool_t {
// 访问底层内存 chunk 的锁「这个层级很重要」
BufListMutex chunks_mutex;
// 各种list的mutex:LRU_list,free_list,flush_list(先说这3个)。。。
BufListMutex LRU_list_mutex;
...
// buffer pool instance index。可能存在多个instance
ulint instance_no;
ulint curr_pool_size;
// old lru list 的起点,这个值是动态变化的【根据3:5计算出来的】
ulint LRU_old_ratio;
...
// hashtable -> 解决如何在 buffer pool 中快速查找page
//「key: tablenum+pagenum, value: page」
hash_table_t *page_hash;
...
// 底层内存交互的单元
buf_chunk_t *chunks;
...
// flush list 基节点
UT_LIST_BASE_NODE_T(buf_page_t) flush_list;
...
// free list 基节点
UT_LIST_BASE_NODE_T(buf_page_t) free;
...
// lru list 基节点
UT_LIST_BASE_NODE_T(buf_page_t) LRU;
// 指向 old 区域头部的指针【取决于BUF_LRU_OLD_MIN_LEN,默认512】
// > BUF_LRU_OLD_MIN_LEN 分新老区域;< 则只有old
buf_page_t *LRU_old;
}
struct buf_chunk_t {
ulint size;
unsigned char *mem;
ut_new_pfx_t mem_pfx;
// 控制块数组 -> 控制块会指向buffer page
buf_block_t *blocks;
...
}
// 非压缩页控制体
struct buf_block_t {
// 注释:一定要放在第一个
// buf_block_t 和 buf_page_t 两种类型的指针可以相互转换
buf_page_t page;
#ifndef UNIV_HOTBACKUP
// 锁
BPageLock lock;
#endif /* UNIV_HOTBACKUP */
// 指向真正存数据的数据页,也是 buffer pool 真正服务的核心
//「数据页,undo page,redo page」等页面的页帧地址
byte *frame;
...
}
至于 压缩页 控制体,本篇就不提及了
【其实也就是
buf_page_t,这个就是为什么提到相互转换,而且把 数据页 放到buf_block_t中存储,因为它是非压缩的。】
至此,基本的数据结构都出来了。
逻辑链表
- free_list
- lru_list
- flu_list
还有几个是关于 压缩页的链表 ,本篇文章不涉及这个问题。
free_list:
其中的节点均为 空闲节点,指向未被分配的
buffer page。
InnoDB需要保证free_list有足够的节点,提供给用户线程用,否则需要从flu_list或者lru_list淘汰一定的节点。
lru_list:
所有新读取进来的
page都被连接在上面。整个链表的改变遵循lru 算法,最近最少使用的节点在末尾,所以在淘汰的时候也是先淘汰末尾。淘汰时机:
free_list中没有空闲节点,则需要淘汰已有page(或者flu_list脏页刷盘)。
lru_list还包含没有被解压的压缩页,这些压缩页刚从磁盘读取出来,还没来的及被解压。
flu_list:
其中所有节点连接的都是
dirty page,也就是这些page都被修改过,但是没有刷新到磁盘。在
flu_list上的页面一定在lru_list上,但是反之则不成立。同时一个
page可以被修改多次。但是要知道,不管修改多少次,第一次修改就会导致page控制指向已经在flu_list,所以flu_list中的脏页是 按照页面的第一次修改时间从大到小进行排序 ,这个在节点中控制块中有以下几个属性:
oldest_modification:第一次修改该页面的事务开始lsn会写入到这个值newest_modification:此后每一次修改页面记录,事务开始lsn会写入到这个值
未完待续。。。