Innodb 架构:Buffer Pool

2024年 2月 7日 52.4k 0

innodb架构

image.png

buffer pool 简介

Buffer pool 是mysql的内存结构之一,如果每次读写都要直接磁盘IO,会大大拖慢执行效率,这就是引入buffer pool的原因。buffer pool的结构如下:

缓存页与磁盘页对应,默认16KB。为了管理这些页,引入了控制块,控制块保存了这些页的元信息,主要有:

  • 表空间编号
  • 页号
  • 缓存页在buffer pool的地址
  • 链表节点指针
  • 锁信息
  • LSN信息
  • 与buffer pool一同参与管理的有三个主要的链表:free链表,flush链表,LRU链表。下面分别介绍。

    缓存页的哈希处理

    如果要访问的数据所在的页已经被加载到buffer pool中,我们就可以直接读写内存。但要如何知道一个页已经被加载到pool中呢?我们通过表空间+页号来标识一个页,因此可以构建一个哈希表:

    key:表空间编号+页号

    value:控制块

    free链表

    如果要加载一页到buffer pool中,如何知道哪个缓存页是空闲的呢?这就是free链表提出的背景。mysql刚启动时,所有的缓存页都处于free链表中,每当从磁盘加载一页到pool中时,就从free链表取出一个空闲的缓存页。把该页对应的控制块信息填上(表空间,页号),然后该缓存页对应的节点从free链表移除。

    flush链表

    如果我们修改了某个页(dirty page),这个页会被加入flush链表,等待刷盘。flush链表的结构和free链表类似。

    LRU链表

    Buffer pool的空间是有限的,如果需要加载一个新页,但free链表已经用光了,该怎么办?这就是LRU链表提出的背景。当缓存页被写入后,该页从free链表移除,加入LRU链表的头部。但这里有两个情况需要考虑:

  • Innodb 提供预读功能,当innodb认为后续请求可能会访问某些页面,它会提前把这些页面加载到buffer pool,但这些页面后续可能用不到,白白占用了空间;

  • 预读分为两种:
  • 线性预读:如果顺序访问某个区的页面超过某个值(innodb_read_ahead_threshold),innodb就会将下个区的全部页面加载到buffer pool;
  • 随机预读:如果buffer pool已经缓存了某个区的13个连续页面,不论这些页面是否是顺序读取的,都会触发一次预读,将本区所有其他页面加载到buffer pool,这个功能通过 innodb_random_read_ahead 控制,默认OFF;
  • 全表扫描,会将很多页面放入buffer pool,将buffer pool换血,但这些页面后续很少被访问到。如果这时还有业务数据在读取其他页面,那这些页面就被从buffer pool挤了出去;

  • 上述两个问题都是劣币驱逐良币,为了解决这一点,LRU链表进行了分区。old区的比例由 innodb_old_blocks_pct 控制,默认值37,代表old区占的比例是37%。

    规则如下:

  • 页加载时会放到old区的头节点
  • 访问old区页面的时候,记录第一次访问的时间,如果后续访问时间与第一次访问时间不超过某个阈值(innodb_old_blocks_time )默认1s,那该页面就不会被移动到young区,否则移动到young区头部
  • 第一点对应预读,避免预读加载的页影响young区域活跃的缓存页;

    第二点对应全表扫描,扫描时某个页会在短时间内被大量访问,之后就不再访问。如果这种高频访问都在阈值内(在一次全表扫描的过程中,多次访问一个页面中的时间不会超过1s),我们就不移动缓存页;

    刷新脏页到磁盘

  • 从LRU链表尾部刷脏(BUF_FLUSH_LRU),后台线程会定时从LRU链表尾部扫描一些页面,扫描数量通过 innodb_lru_scan_depth 控制,如果这些页里有脏页,会把他们刷新到磁盘;
  • 从flush链表刷脏(BUF_FLUSH_LIST),后台线程会定时从flush链表刷脏;
  • 有时候后台线程刷脏比较慢,导致用户线程加载页面时没有可用的缓存页,这时用户线程就会尝试将LRU链表尾部的脏页同步刷新到磁盘,这被称作BUF_FLUSH_SINGLE_PAGE,是个很慢的操作。

    多个buff pool实例

    为了提高并发度,innodb_buffer_pool_instances 管理buff pool实例个数,每个实例是彼此独立的。每个buffer pool占用的空间:

    innodb_buffer_pool_size / innodb_buffer_pool_instances
    

    随着多实例的引入,还提出了chunk的概念,每次申请内存以chunk为单位:

    innodb_buffer_pool_chunk_size 设置了chunk的大小,默认128M。

    innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数,这是为了保证每个buffer pool实例包含的chunk数量相同

    Buff pool 状态信息

    mysql> SHOW ENGINE INNODB STATUSG
    ----------------------
    BUFFER POOL AND MEMORY
    ----------------------
    Total memory allocated 13218349056;
    Dictionary memory allocated 4014231
    Buffer pool size   786432
    Free buffers       8174
    Database pages     710576
    Old database pages 262143
    Modified db pages  124941
    Pending reads 0
    Pending writes: LRU 0, flush list 0, single page 0
    Pages made young 6195930012, not young 78247510485
    108.18 youngs/s, 226.15 non-youngs/s
    Pages read 2748866728, created 29217873, written 4845680877
    160.77 reads/s, 3.80 creates/s, 190.16 writes/s
    Buffer pool hit rate 956 / 1000, young-making rate 30 / 1000 not 605 / 1000
    Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
    LRU len: 710576, unzip_LRU len: 118
    I/O sum[134264]:cur[144], unzip sum[16]:cur[0]
    
  • Total memory allocated:buffer pool 总大小;
  • buffer pool size:Buffer Pool 可以容纳多少缓存页,注意,单位是页!
  • Free buffers:还有多少空闲页,也就是free链表节点数;
  • Database pages:LRU链表页数量;
  • Old database pages:LRU old区页数量;
  • Modified db pages:脏页数量,也就是flush链表节点数;
  • Pending reads:等待从磁盘加载的页面数量;
  • Pending writes:即将从(LRU,flush链表,单页)刷新到磁盘的页数量;
  • Pages read,created,written:读取,创建,写入了多少页。后边跟着读取、创建、写入的速率;
  • Buffer pool hit rate:缓存命中率;
  • I/O sum:最近50s读取磁盘页的总数;
  • I/O cur:现在正在读取的磁盘页数量;
  • 参考

  • 第18章 调节磁盘和CPU的矛盾-InnoDB的Buffer Pool (relph1119.github.io)
  • dev.mysql.com/doc/refman/…
  • 相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论