一、buffer pool
1.1 缓冲池(buffer pool)
- 简介:InnoDB是基于磁盘存储的,并将其中的数据按页的方式进行管理。因此InnoDB可视为基于磁盘的数据库系统。由于CPU的速度和磁盘IO速度的巨大鸿沟,需要**缓冲池(buffer pool)**来提高数据库的整体性能
- 作用:为了提高大容量读取操作的效率,缓冲池被分为多个页,这些页可能包含多个行。为了提高缓存管理的效率,缓冲池被实现为页的链接列表。使用LRU算法的变体将很少使用的数据从缓存中老化掉 。
- 架构:
-
原理
- 第一次读取数据时,首先从磁盘中读取数据页,并放到(FIX)缓冲池中。
- 再次读取同样的数据时,先看缓冲池中是否有相同的数据页。有则命中,从缓冲池中读取。否则从磁盘读取
-
缓存页数据结构
缓存页都会对应着一个描述数据块,里面包含数据页所属的表空间、数据页的编号,缓存页在
Buffer Pool
中的地址等等。描述数据块本身也是一块数据,它的大小大概是缓存页大小的5%左右,大概800个字节左右的大小。
1.2 buffer pool的缓存结构
每个 Buffer Pool
负责管理着自己的描述数据块和缓存页,有自己独立一套 free 链表、flush 链表和 lru 链表。
上面我们知道缓冲池被设计为页的链表结构根据页的不同状态(未使用,已使用,已经使用未刷新到缓存):
- FREE链表: 空闲列表,页属于缓存池但是并没有缓存mysql数据(查询出来的数据会使用该链表的节点存储数据并从free链表移除 进入到LRU链表)。
- FLUSH链表: LRU列表中的数据一旦被修改(和磁盘不一致),则该页被标记为flush链表中的一员。
- LRU链表: 采用最近最久未使用LRU的变种(新增了midPoint)内存淘汰策略。
1.2.1 LRU list
基于LRU(最近最久未使用)内存淘汰策略并新增了midPoint位置。新读取到的页并没有直接放在LRU列的首部,而是放在距离尾部37%的位置。这个算法称之为midpoint insertion stategy。
1.2.2 Free list
双向链表,链表的每个节点就是一个个空闲的缓存页对应的描述数据块。由 Buffer Pool 里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是 free_pre 指针,一个是 free_next 指针,分别指向自己的上一个 free 链表的节点,以及下一个 free 链表的节点。
free 链表有一个基础节点,他会引用链表的头节点和尾节点,里面还存储了链表中有多少个描述数据块的节点,也就是有多少个空闲的缓存页。
1.2.3 Flush list
Buffer Pool
中的缓存页因为不断被修改而导致和磁盘文件中的数据不一致了会有很多个脏页,脏页里面很多脏数据。所以,MySQL 会有一条后台线程,定时地将 Buffer Pool
中的脏页刷回到磁盘文件中。
1.3 Buffer Pool 高并发场景
单个 Buffer Pool 的问题
如果 InnoDB 存储引擎只有一个 Buffer Pool,当高并发时,多个请求进来,那么为了保证数据的一致性(缓存页、free 链表、flush 链表、lru 链表等多种操作),必须得给缓冲池加锁了,每一时刻只能有一个请求获得锁去操作 Buffer Pool,其他请求只能排队等待锁释放。那么此时 MySQL 的性能是多么的低!
多个 Buffer Pool
在生产环境中,其实我们是可以给 MySQL 设置多个 Buffer Pool 来提升 MySQL 的并发能力的
如果Buffer Pool 分配的内存小于1GB,那么最多就只会给你一个 Buffer Pool。
但是呢,如果你给 MySQL 设置的内存很大,此时你可以利用下面两个参数来设置 Buffer Pool 的总大小和总实例数,这样,MySQL 就能有多个 Buffer Pool 来支撑高并发了。
二、Mysql持久性的实现
2.1、持久性的定义
事务一旦提交,则其所有的修改将会保存到数据库当中。即使此时系统崩溃,修改的数据也不会丢失。同时数据库连接中,默认有一个参数autocommit=1(如果想要关掉,要set autocommit=0,然后要手动的开启关闭),表示每次执行一条sql如果没有显示启动事务语句(begin或start transaction)就会隐试的开启一个事务。
redo log 和 undo log 都属于InnoDB的事务日志。
- redo log是用来恢复数据的,用于保障已提交事务的持久性;
- undo log是用来回滚事务的,用于保障未提交事务的原子性。
2.2 redo log(重做日志) 如何保证事务的持久性?
InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,redo log 被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在 redo log 记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然 redo log 也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?
- 刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO
- 刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
MySQL会根据redo log自动在系统空闲时完成更新磁盘数据页的操作, 这个过程也叫做刷盘, 会对MySQL性能有一定影响
你对数据页的修改都记录在redo log了, 宕机了根据redo log的内容恢复就好, 默认设置下事务只要提交就会持久化到磁盘文件redo log中。
redo log持久化策略
2.3 undo log(回滚日志) 如何来保证事务的原子性?
原子性指的是事务中的所有操作是一个不可分割的整体, 要么全做, 要么全不做.
利用Innodb引擎的undo log(回滚日志),它记录了回滚一个操作必需的内容
比如,当你update一条数据的时候,就需要这条记录的原始值,回滚的时候,把这条记录再update为原始值
这样,当事务执行失败,就可以根据undo log回滚之前执行成功的操作。
InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
undo log 属于逻辑日志,它记录的是sql执行相关的信息。可以简单的理解为:当insert一条记录时,undo log会记录一条对应的delete语句;当update一条语句时,undo log记录的是一条与之操作相反的语句。当事务需要回滚时,可以从undo log中找到相应的内容进行回滚操作,回滚后数据恢复到操作之前的状态。
2.4 redo log与binlog的区别
redo log又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。
binlog(主要用于集群、主从)记录了对MySQL数据库执行更改的所有操作,但是不包括SELECT和SHOW这类操作,因为这类操作对数据本身并没有修改。然后,若操作本身并没有导致数据库发生变化,那么该操作也会写入二进制日志。
2.5 checkpoint机制
checkpoint触发的时候,主要干两件事:
- 将Buffer Pool中的脏页刷新到磁盘上
- 将对应的脏页信息和活跃事务信息写入到red olog日志中
checkpoint有以下好处:
- 可以缩短数据库的恢复时间
- 当buffer pool中的内存不够用时,刷新脏页到磁盘中
- 当redolog file不够用时,将脏页刷新到磁盘中