数据库架构之Buffer Pool

Buffer Pool简介和工作流程

Posted by Ted on December 10, 2022

1、概念

InnoDB的Buffer Pool(缓冲池)是MySQL数据库中非常重要的一个组件,它主要用于缓存数据和索引,以提高数据库的性能。

以下是Buffer Pool的主要作用:

  • 数据缓存:当你查询一个数据行时,InnoDB首先会在Buffer Pool中查找这个数据行。如果找到,那么就直接从内存中读取,这比从磁盘读取要快得多。如果在Buffer Pool中没有找到,那么InnoDB会从磁盘读取这个数据行,并将其放入Buffer Pool中,以便下次查询时能够直接从内存中读取。
  • 索引缓存:除了数据行,InnoDB还会在Buffer Pool中缓存索引。这样,当你执行一个需要使用索引的查询时,InnoDB可以直接从内存中读取索引,而不需要从磁盘读取。
  • 写操作的缓存:当你执行一个写操作(如INSERT、UPDATE或DELETE)时,InnoDB会先将修改写入到Buffer Pool中,然后在适当的时机(如在事务提交时或在做Checkpoint时)再将这些修改写入到磁盘。这种方式可以减少磁盘I/O操作,从而提高性能。
  • 读写操作的并发控制:Buffer Pool中的每一个数据页都有对应的锁和读写控制机制,这样可以支持高并发的读写操作。

总的来说,InnoDB的Buffer Pool是一个非常重要的性能优化组件,它通过缓存数据和索引,以及优化磁盘I/O操作,来提高数据库的性能。

img

总结

Buffer Pool就是数据库的一个内存组件,里面缓存了磁盘上的真实数据,然后我们的系统对数据库执行的增删改操作,其实主要就是对这个内存数据结构中的缓存数据执行的。通过这种方式,保证每个更新请求,尽量就是只更新内存,然后往磁盘顺序写日志文件。

更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是比较高的,因为顺序写磁盘文件,他的性能要远高于随机读写磁盘文件。

2、buffer pool的工作流程

这条 SQL 语句的执行步骤大致是这样子的

  • innodb 存储引擎会在缓冲池中查找 id=1 的这条数据是否存在
  • 发现不存在,那么就会去磁盘中加载,并将其存放在缓冲池中
  • 该条记录会被加上一个独占锁

img

2.1 buffer pool VS 查询缓存

在mysql8.0的版本中,已经将查询缓存模块删除了。

如果将Mysql分为Server层和存储引擎层两大部分,那么Qcache位于Server层,Buffer Pool位于存储引擎层。

  如果你的Mysql 查询缓存功能是打开的,那么当一个sql进入Mysql Server之后,Mysql Server首先会从查询缓存中查看是否曾经执行过这个SQL,如果曾经执行过的话,曾经执行的查询结果之前会以key-value的形式

保存在查询缓存中。key是sql语句,value是查询结果。我们将这个过程称为查询缓存!

  如果查询缓存中没有你要找的数据的话,MySQL才会执行后续的逻辑,通过存储引擎将数据检索出来。并且查询缓存会被shared cache for sessions,是的,它会被所有的session共享。

  MySQL查询缓存是查询结果缓存。它将以SEL开头的查询与哈希表进行比较,如果匹配,则返回上一次查询的结果。进行匹配时,查询必须逐字节匹配,例如 SELECT * FROM t1; 不等于select * from t1;,此外,一些不确定的查询结果无法被缓存,任何对表的修改都会导致这些表的所有缓存无效(只要有一个sql update了该表,那么表的查询缓存就会失效)。因此,适用于查询缓存的最理想的方案是只读,特别是需要检查数百万行后仅返回数行的复杂查询。如果你的查询符合这样一个特点,开启查询缓存会提升你的查询性能。

  MySQL查询缓存的目的是为了提升查询性能,但它本身也是有性能开销的。需要在合适的业务场景下(读写压力模型)使用,不合适的业务场景不但不能提升查询性能,查询缓存反而会变成MySQL的瓶颈。

查询缓存的开销主要有:

  • 读查询在开始前必须先检查是否命中缓存;
  • 如果这个读查询可以被缓存,那么当完成执行后,MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统消耗;
  • 当向某个表写入数据的时候,MySQL必须将对应表的所有缓存都设置失效。如果查询缓存非常大或者碎片很多,这个操作就可能带来很大的系统消耗。

查询缓存的缺点:

  • 首先,查询缓存的效果取决于缓存的命中率,只有命中缓存的查询效果才能有改善,因此无法预测其性能。只要有一个sql update了该表,那么表的查询缓存就会失效,所以当你的业务对表CRUD的比例不相上下,那么查询缓存may be会影响应用的吞吐效率。
  • 其次,查询缓存的另一个大问题是它受到单个互斥锁的保护。在具有多个内核的服务器上,大量查询会导致大量的互斥锁争用。
  • 在mysql8.0的版本中,已经将查询缓存模块删除了。

2.2 undo 日志文件:记录数据被修改前的样子

在准备更新一条语句的时候,该条语句会先被加载到 Buffer pool 中了,同时,在将该条语句加载到 Buffer Pool 中的时候同时会往 undo 日志文件中插入一条日志,也就是将 id=1 的这条记录的原来的值记录下来。

这样做的目的是什么?

Innodb 存储引擎的最大特点就是支持事务,如果本次更新失败,也就是事务提交失败,那么该事务中的所有的操作都必须回滚到执行前的样子,也就是说当事务失败的时候,也不会对原始数据有影响,

2.3 redo 日志文件:记录数据被修改后的样子

redo 日志文件是 InnoDB 特有的,他是存储引擎级别的,不是 MySQL 级别的

redo 记录的是数据修改之后的值,不管事务是否提交都会记录下来。MySQL 为了提高效率,所以将这些操作都先放在内存中redo log buffer去完成,然后会在某个时机将其持久化到磁盘中。如果 MySQL 真的宕机了,那么没关系的,因为 MySQL 会认为本次事务是失败的,所以数据依旧是更新前的样子,并不会有任何的影响。

语句也更新好了那么需要将更新的值提交啊,也就是需要提交本次的事务了,因为只要事务成功提交了,才会将最后的变更保存到数据库,在提交事务前仍然会具有相关的其他操作:将 redo Log Buffer 中的数据持久化到磁盘中,就是将 redo log buffer 中的数据写入到 redo log 磁盘文件中,一般情况下,redo log Buffer 数据写入磁盘的策略是立即刷入磁盘

2.4 MYSQL更新数据的过程

截至目前,我们应该都熟悉了 MySQL 的执行器调用存储引擎是怎么将一条 SQL 加载到缓冲池和记录哪些日志的,流程如下:

  1. 准备更新一条 SQL 语句

  2. MySQL(innodb)会先去缓冲池(BufferPool)中去查找这条数据,没找到就会去磁盘中查找,如果查找到就会将这条数据加载到缓冲池(BufferPool)中

  3. 在加载到 Buffer Pool 的同时,会将这条数据的原始记录保存到 undo 日志文件中

  4. innodb 会在 Buffer Pool 中执行更新操作

  5. 更新后的数据会记录在 redo log buffer 中

  6. MySQL 提交事务的时候,会将 redo log buffer 中的数据写入到 redo 日志文件中 刷磁盘可以通过 innodb_flush_log_at_trx_commit 参数来设置

    • 值为 0 表示不刷入磁盘

    • 值为 1 表示立即刷入磁盘

    • 值为 2 表示先刷到 os cache

  7. myslq 重启的时候会将 redo 日志恢复到缓冲池中

img

3、总结:

我们再回顾下

  • Buffer Pool 是 MySQL 的一个非常重要的组件,因为针对数据库的增删改操作都是在 Buffer Pool 中完成的
  • Undo log 记录的是数据操作前的样子
  • redo log 记录的是数据被操作后的样子(redo log 是 Innodb 存储引擎特有)
  • bin log 记录的是整个操作记录(这个对于主从复制具有非常重要的意义)

具体更新一条记录 UPDATE t_user SET name = ‘xiaolin’ WHERE id = 1; 的流程如下:

  1. 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
  • 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
  • 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
  1. 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
  • 如果一样的话就不进行后续更新流程;
  • 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
  1. 开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
  2. InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
  3. 至此,一条记录更新完了。
  4. 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
  5. 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
  • prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
  • commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件);
  1. 至此,一条更新语句执行完成。