Innodb 架构:Buffer Pool

innodb架构

buffer pool 简介

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

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

  1. 表空间编号
  2. 页号
  3. 缓存页在buffer pool的地址
  4. 链表节点指针
  5. 锁信息
  6. 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链表的头部。但这里有两个情况需要考虑:

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

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

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

规则如下:

  1. 页加载时会放到old区的头节点
  2. 访问old区页面的时候,记录第一次访问的时间,如果后续访问时间与第一次访问时间不超过某个阈值(innodb_old_blocks_time )默认1s,那该页面就不会被移动到young区,否则移动到young区头部

第一点对应预读,避免预读加载的页影响young区域活跃的缓存页;

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

刷新脏页到磁盘

  1. 从LRU链表尾部刷脏(BUF_FLUSH_LRU),后台线程会定时从LRU链表尾部扫描一些页面,扫描数量通过 innodb_lru_scan_depth 控制,如果这些页里有脏页,会把他们刷新到磁盘;
  2. 从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 状态信息

bash 复制代码
mysql> SHOW ENGINE INNODB STATUS\G
----------------------
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]
  1. Total memory allocated:buffer pool 总大小;
  2. buffer pool size:Buffer Pool 可以容纳多少缓存页,注意,单位是页!
  3. Free buffers:还有多少空闲页,也就是free链表节点数;
  4. Database pages:LRU链表页数量;
  5. Old database pages:LRU old区页数量;
  6. Modified db pages:脏页数量,也就是flush链表节点数;
  7. Pending reads:等待从磁盘加载的页面数量;
  8. Pending writes:即将从(LRU,flush链表,单页)刷新到磁盘的页数量;
  9. Pages read,created,written:读取,创建,写入了多少页。后边跟着读取、创建、写入的速率;
  10. Buffer pool hit rate:缓存命中率;
  11. I/O sum:最近50s读取磁盘页的总数;
  12. I/O cur:现在正在读取的磁盘页数量;

参考

  1. 第18章 调节磁盘和CPU的矛盾-InnoDB的Buffer Pool (relph1119.github.io)
  2. dev.mysql.com/doc/refman/...
相关推荐
摇滚侠2 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯4 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友5 小时前
什么是断言?
前端·后端·安全
程序员小凯6 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
闲人编程6 小时前
从多个数据源(CSV, Excel, SQL)自动整合数据
python·mysql·数据分析·csv·存储·数据源·codecapsule
i学长的猫6 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636027 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
disanleya7 小时前
MySQL默认密码不安全?如何首次登录并强化?
数据库·mysql·安全
花开富贵贼富贵7 小时前
MySQL 核心高级特性
运维·数据库·mysql
茯苓gao7 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django