读《MySQL技术内幕:InnoDB存储引擎(第2版)》学习笔记
InnoDB 存储引擎体系架构
InnoDB存储引擎主要由后台线程、内存池、和磁盘存储组成
后台线程
InnoDB是多线程的模型 ,不同线程负责不同的任务,分为Master thread、IO Thread、和Purge Thread。
Master thread
主要负责将内存中的缓存数据,刷新到磁盘,刷新策略:
每秒的操作包括:
- 重做日志缓冲刷新到磁盘(重做日志文件),即使这个事务还没有提交
- 合并插入缓冲
- 至多刷新100个InnoDB的缓冲池中的脏页到磁盘
每十秒的操作包括:
- 刷新100个脏页到磁盘
- 合并至多5个插入缓冲
- 将重做日志缓冲刷新到磁盘
- 执行 full purge 操作,删除无用的undo页
- 刷新100个或10个脏页到磁盘
IO Thread
InnoDB 大量使用 AIO(Async IO)来处理写 IO 请求,这样可以提高数据库的性能。IO Thread主要负责这些IO请求的回调处理。
Purge Thread
事务被提交后,其使用的undo log可能不再需要,因此需要Purge Thread回收已经使用并分配的undo页。
内存池的管理
缓冲池
简单来说,缓冲池就是一块内存区域,通过内存来弥补磁盘速度较慢对数据库性能的影响。
读取操作,首先将从磁盘读取到的页存放到缓冲池中,下次读取相同的页时,首先判断该页是否在缓冲池中,若在,称该页在缓冲池中被命中,直接读取缓冲池的该页,否则,读取磁盘。
修改操作,先修改缓冲池中的页,然后以一定的频率刷新到磁盘。而页的刷新操作不是每次修改时触发,而是通过 CheckPoint 的机制刷新回磁盘。
LRU List、Free List和 Flush List
缓冲池是一个很大的内存区域,其中存放各种类型的页。InnoDB是怎么管理这么大的内存区域?
通常来说,数据库中的缓冲池是通过 LRU 算法管理的。即最频繁最近使用的页在 LRU 列表的前端,而最少使用的页在 LRU 列表的尾端。当缓冲池中不能存放新读取到的页时,将释放LRU列表尾端的页。
InnoDB在 LRU 列表中加入了 midpoint 位置,新读取的页,虽然是最近访问的页,但并不是直接放入到 LRU 列表的首部,而是放入到 LRU列表的midpoint位置,默认情况下是在 LRU列表长度的 5/8 处。因为新读取的数据,不一定就是最活跃的数据,不适合直接放到列表的首部。
LRU 列表用于管理已经读取的页。但数据库刚刚启动时,LRU列表是空的,这时,页都在Free 列表中,当需要从缓冲池中分页时,先从Free List 中查找是否有可用的空闲页,若有,将该页从 Free List中删除,放入 LRU 列表中。否则,根据LRU,淘汰LRU末尾的页,将该内存空间分配给新的页。
在 LRU 列表中的页被修改后,称为脏页,即缓冲池中的页和磁盘的页数据产生了不一致。这时,数据库会通过 Checkpoint机制将脏页刷新回磁盘,而 Flush 列表中的页即为脏页列表。需要注意的是,脏页既存在于 LRU 列表中,也存在于 Flush 列表。LRU 列表用来管理缓冲池中页的可用性,Flush用来管理将页刷新回磁盘,二者不影响。
重做日志缓冲
InnoDB首先将重做日志先放入到这个缓冲区,然后按照一定的频率将其刷新到磁盘上的重做日志文件。默认大小为 8 MB,以下三种情况,会对重做日志进行刷新。
- Master Thread 每秒对重做日志缓冲刷新
- 每个事务提交时,对重做日志缓冲刷新
- 当重做日志缓冲池剩余空间小于 1/2 时
Checkpoint 技术
checkpoint的目的是将缓冲池中的脏页刷回到磁盘,Checkpoint 技术解决以下几个问题:
- 缩短数据库的恢复时间
- 缓冲池不够用时,将脏页刷新回磁盘
- 重做日志不可用时,刷新脏页
什么时间触发checkpoint?
- 发生在数据库关闭时将所有的脏页刷新回磁盘,这是默认的工作方式
- Master Thread差不多以每秒或每十秒的速度从缓冲池中的脏页列表中刷新一定比例的页回磁盘
- 如果空闲页小于100,那么将LRU列表尾部的页移除,如果这些页中有脏页,那么需要 Checkpoint
- 脏页太多,导致InnoDB 存储引擎强制 Checkpoint,目的是为了保证缓冲池有足够可用的页,默认为 75%
双写
两次写可提高InnoDB存储引擎数据页的可靠性。当数据库发生宕机时,可能InnoDB正在写入某个页到表中,而这个页只写了一部分,之后就发生了宕机,这种情况被称为部分写失效。如果发生写失效,是不是可以通过重做日志进行恢复呢?但需要知道的是,重做日志中记录的是对页的物理操作,如偏移量800写’qqq’记录。如果这个页本身发生了损坏,再对其进行重做是没有意义的。
doublewrite有两部分构成,一部分是内存中的 doublewrite buffer,大小为 2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即两个区(extent),大小同样为 2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存的 doublewrite buffer,然后通过doublewrite buffer再分两次,每次1MB顺序写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。
在以上的过程中,因为doublewrite是连续的,因此这个过程是顺序写,开销不是很大,完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中,此时的写是离散的。
如果数据库在重启的过程中,发现磁盘上表空间的数据页发生了损坏,就会使用共享表空间中的副本,替换损坏的数据页。
重做日志redo log是不是也使用了双写的技术呢?没有这个必要,因为重做日志刷新到磁盘的最小单位是一个扇区(大小是512B),操作系统能够保证一个扇区的数据存储到存盘的原子性。