InnoDB Buffer Pool「一」|小册免费学

·  阅读 890

本文针对的是 mysql innodb 特性,结合源码

先明白几个点:

  1. 真实物理数据存在磁盘中。不管是固态还是机械硬盘。
  2. sql 获取的是每一行数据,但是 server 从磁盘中获取单位是 page 。一个 page 包含多个 record
  3. 增删改查,server 操作的地方都在内存

上述想说明什么?数据在磁盘,但是操作数据在内存,同时获取数据到内存是以 page 为单位。

从日常业务中看,redis 是解决服务获取数据库数据 I/O 延迟消耗,从而把数据从 DB -> redis。然后我们看回到这里,那么 innodb 如果要缓存数据,也需要一个 缓存池,用于管理相应的 page,提高数据库的效率。

这个就是 buffer pool,当然也因为引入这个中间层,管理也变得相当复杂:

  • 内存管理【预热,加入,淘汰】
  • 并发访问
  • 一致性问题【修改了数据(内存),磁盘数据一致性怎么处理】

基础知识

github.com/mysql/mysql…

这个是 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 会写入到这个值

未完待续。。。

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改