innodb(3) 缓冲池

1. 前言

innodb存储引擎是基于磁盘存储的, 并将其记录按照页的方式进行管理, 但是由于cpu速度与磁盘速度之间的鸿沟, 基于磁盘的数据库系统通常使用缓冲技术来提高数据的整体性能

在数据库中进行读取页的操作, 首先将从磁盘读到的页存放在缓冲池中, 下一次读取相同的页时, 首先判断该页是不是在缓冲池中, 若在称该页在缓冲池中被命中, 直接读取该页, 否则读取磁盘上的页

对于数据库中页的修改操作, 首先修改在缓冲池中的页, 然后再以一定的频率刷新到磁盘, 并不是每次页发生改变就刷新回磁盘, 而是通过一种叫做checkpoint的机制把页刷新回磁盘, 下面再详解

因此数据的操作是对缓冲池进行的操作, 而不是磁盘

缓冲池的大小设置 :  INNODB_BUFFER_POOL_SZIE

2. 简介与结构分析

缓冲池是一块内存, innodb存储引擎是通过页的方式对这块内存进行管理

缓冲池中存储的页有 : 

  • 索引页
  • 数据页
  • 插入缓存
  • 自适应哈希索引
  • innodb存储的锁信息
  • 数据字典信息

允许有多个缓冲池实例, 每个页根据哈希值平均分配到不同的缓冲池实例中

缓冲池个数设置 : innodb_buffer_pool_instances

3. 缓冲池管理

q : 如何将磁盘上的页缓存到内存中的buffer pool中呢?

为了更好管理这些被缓存的页, innodb为每一个缓存页都创建了一些所谓的控制信息, 主要包括

  • 表空间编号 (space id)
  • 页号 (page number)
  • 页在buffer pool中的地址等

每一个缓存页对应控制信息占用的内存大小是相同的,

控制块和缓存页是一一对应的,都被存放在buffer pool中,

控制块被放在bp前边, 缓存页被存放在bp后边

q : 碎片是什么?

每一个控制块对应一个缓存页, 那么分配足够多的控制块和缓存页后, 可能剩余的那点空间不够一对控制块和缓存页的大小, 如果把buffer pool的大小设置刚刚好的话, 也可能不会产生碎片

 


当我们最初启动mysql服务器的时候, 需要完成对bp的初始化过程, 就是分配bp的内存空间,把它划分成若干对控制块和缓存页,但是此时并没有真是的磁盘页被存储到bp中, 之后随着程序的运行, 会不断的有磁盘上的页被缓存到bp中

q : 怎么区分bp中哪些缓存页是空闲的? 哪些是被使用的? 

通过Free链表, 我们把所有空闲页包装成一个节点, 组成一个链表, mysql服务器刚启动时, 所有的空闲页都在free链表中

有了这个free链表, 每当需要从磁盘中加载一个页到bp中时, 就从free链表取一个空闲的缓存页,并且把这个缓存页对应的控制块的信息填上, 然后把该缓存页对应的free链表节点从链表中移除,表示该缓存页已经被使用, 并且把该页写入LRU链表

q : 采用什么方式删除缓冲池中的数据? 究竟该删除哪些已经缓存的页 ?

LRU链表

  • 如果该页不在bp中, 再把该页从磁盘加载到bp中的缓存页时, 就把该缓存页包装成节点, 塞到链表的头部
  • 如果该页在bp中, 则直接把该页对应的LRU链表节点移动到链表的头部

LRU算法优化

  • 加入midpoint概念, 新访问的页不会直接插入头部, 而是插入midpoint位置, 默认配置到列表长度的5/8处
  • midpoint之前成为new 列表, 之后称为old列表 (热点数据与非热点数据)
  • innodb_old_blocks_time来表示页读取到mid位置之后需要等待多久才会被加入到LRU列表的热端。可以通过设置该参数保证热点数据不轻易被刷出。

q : 脏页什么时候刷新到磁盘 ? 

再创一个存储脏页的链表 flush链表 !!! 凡是在LRU链表中被修改过的页, 都需要加入到这个链表中

这里指的脏页是指此页被加载到bp后的第一次修改, 只有第一次被修改时才需要加入到flush链表,如果这个页别再次修改就不会再放到flush链表了, 因为已经存在

需要注意的是, 脏页数据实际还在LRU链表中, 而flush链表中的脏页记录只是通过指针指向LRU链表中的脏页

4. 缓存中页的定位

q : 我们如何判断我们要查询的数据是否在bp中呢? 难道要依次遍历bp中的各个缓存页吗?

我们其实是根据表空间号 + 页号来定位一个页的, 也就相当于表空间号 + 页号是一个key, 缓存页是对应的value, 说到这里想必大家已经明白啦, 有key和value, 肯定是哈希表

mysql > show engine innodb status

5. checkpoint

当我们从磁盘中读取一页数据之后, 这一个页就会被刷到bp, 进行保存, 下次再次访问的时候, 通过hash表来判断该页是否在bp中, 我们每次从磁盘读取数据都会刷新到bp中, 这样就会有一个问题.

q : 如果free列表中没有空闲的页来提供bp保存数据, 那么如何生成空闲的页呢 ?

  • 去LRU列表中查找可以替换的内存页, 从尾部开始查找
  • 单页刷新

将释放的内存页加入到free列表, 然后再在列表中取空闲页, 但是因为空闲列表是一个公共的列表, 所有的用户线程都可以使用, 存在竞争的情况, 因此自己产生的空闲页可能刚好被其他线程所使用

q : 为什么只做单页刷新 ?

单页刷新的目的是为了获取空闲页, 进行脏页刷新是迫不得已的, 所以只会进行一个页面的刷新,  目的是为了尽快获取空闲页

当前事务数据库系统普遍采用了write ahead log策略, 即当事务提交时, 先写重做日志, 在修改页, 当由于数据库宕机而导致数据丢失时, 通过重做日志来完成数据的回复

想一种情况, 当重做日志变的很大时, 数据库的回复时间会变的很长, 恢复代价变的很大

checkpoint技术主要目的是 : 

  • 缩短数据库的回复时间
  • 缓冲池不够用时, 将脏页刷新到磁盘
  • 重做日志不可用时, 刷新脏页

当数据库宕机时, 数据库不需要所有的重做日志, 因为checkpoint之前的页都已经刷新回磁盘, 故数据库只需要对checkpoint之后的重做日志进行恢复即可

  • 当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。数据库只需对Checkpoint后的重做日志进行恢复,这样就大大缩短了恢复的时间。

  • 当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。

  • 当重做日志出现不可用时,因为当前事务数据库系统对重做日志的设计都是循环使用的,并不是让其无限增大的,重做日志可以被重用的部分是指这些重做日志已经不再需要,当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。如果重做日志还需要使用,那么必须强制Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置

innodb存储引擎内部有两种checkpoint方式 : 

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint发生将在数据库关机时, 将所有的脏页刷新回磁盘,

Fuzzy Checkpoint的刷新方式每次只刷新一部分脏页, 而不是刷新所有的脏页

以下四种情况会执行刷新脏页到磁盘 : 

  • master thread线程会以每一秒或每十秒的速度将一定比例的脏页刷新到磁盘
  • 至少要保证LRU列表中有100个空闲页可供使用, 如果小于100个空闲页的时候, 会将列表末端页移除, 如果有脏页则刷新
  • 当重做日志不可用的情况下, 会强制将一些脏页刷新到磁盘
  • 当脏页的数量太多, 导致innodb存储引擎强制checkpoint

6. innodb的关键特性

① 插入缓存 (insert buffer)

用于非聚簇索引的插入和更新操作

先判断插入的非聚簇索引是否在缓冲池中, 如果在则直接插入, 否则插入到insert buffer中, 再以一定的频率进行insert buffer和辅助索引叶子节点的merge操作, 将多次插入合并到一个操作中, 提高了非聚簇索引的插入性能

使用insert buffer要满足的条件

  • 索引是辅助索引
  • 索引不唯一 (如果唯一, 还要去检查唯一性, 就是失去了性能)

扩展 : 

change buffer

当我们执行一条更新语句时, 会将数据页从磁盘取出放入到bp中进行更新, 但是如果我们这个表有多个索引, 那么对应的索引页也需要更新, 那么io的次数就会增多, 降低更新效率, 因此采用了change buffer进行优化, 只更新数据页不更新索引页, 并将update语句存入到change buffer中, 此时索引页中为脏数据.  当我们通过索引页获取数据时, 将数据与change buffer中进行对比, 更新索引页中的数据, 返回正确数据

② 二次写

如果数据库发生宕机时, 可以通过重做日志对该页进行恢复, 但是如果该页本身已经损坏了, 进行重做恢复是没有意义的, 因此引入了二次写方案, 提高数据页的稳定性

doublewrite流程  : 

  1. 对缓存池脏页进行刷新时, 不直接写入磁盘, 而是将脏页不知道内存中的doublewrite buffer中
  2. 将内存中的doublewrite buffer写入共享表空间的物理磁盘上 (备份 )
  3. 将doublewrite buffer中的数据真正的刷新到表磁盘中
  4. 如果写doublewrite buffer 失败, 那么这些数据不会写到磁盘, innodb会载入磁盘原始数据和redo日志比较, 并重新刷到doublewrite buffer中
  5. 如果写doublewrite buffer成功, 但是刷新到磁盘失败, 那么innodb就不会通过redo日志来恢复了, 而是直接刷新doublewrite buffer中的数据到磁盘
  6. skip_inno_double_written 参数用来禁止二次写功能

 如果第一步还没写入成功 , mysql宕机, 则通过redo log进行数据恢复

如果第一步成功, 第二步没有成功, 但是此时double write buffer存在磁盘, 可以通过他进行数据恢复

随着硬件的发展, 很多硬件可以支持原子性写, 可以试着关闭double write功能, 提高性能

③ 自适应哈希索引

④ 异步 IO

为了提高磁盘操作的性能, innodb存储引擎采用异步IO的方式来处理磁盘操作 : 

  • 可以减少查询时间
  • 可以对多个IO进行合并操作 ( insert buffer 写非聚簇索引)

⑤ 刷新邻接页

刷新邻接页功能是指在刷新脏页时 , 检查该页所在区的所有页是否存在脏页, 存在则一起刷新, 可以这样通过 AIO进行脏页合并刷新

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值