mysql学习笔记(一):innoDB存储引擎

180 阅读7分钟

InnoDB体系架构

主要有后台线程,内存池和文件三部分组成

1575439755466

后台线程

负责刷新内存池中的数据

将已修改的数据文件刷新到磁盘文件

保证再数据库发生异常的情况下InnoDB能恢复到正常运行状态

  1. Master Thread

    主要负责将缓存池中的数据异步刷新到磁盘,保证数据的一致性

    脏页的刷新

    合并插入缓存(insert buffer)

    undo页的回收

  2. IO Thread

    AIO来处理写请求

  3. Purge Thread

    事务呗提交后,其所使用的undolog可能不在需要,因此需要PurgeThread来回收已经使用并分配的undo页

  4. Page Cleaner Thread

    1.2.X版本中引入,完成脏页的刷新操作,减轻Master Thread的工作

内存池

缓冲池

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。

缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等

1575440411197

LRU List、Free List和Flush List

LRU List

缓冲池不可能缓存所有磁盘上的数据,这时候就需要通过LRU(Latest Recent Used,最近最少使用)算法来进行管理。即最频繁使用的页再LRU列表的前端

缓冲池中页的大小默认是16KB

InnoDB存储引擎对传统的LRU算法锁了一些优化,加入了midpoint位置。新读取到的页,不会直接放入到LRU列表的首部,而是放在LRU列表的midpoint位置,大概在5/8的位置

1575440882699

Free List

LRU管理已经读取的页,数据库启动时,LRU列表是空的,没有页。此时的页都放在Free list。

当需要从缓冲池中分页时,先查看Free list是否有空闲的页,若有,则从Free list中删除该页,放入到LRU列表。若没有,则根据LRU算法,释放LRU列表尾端的页,将该内存分给新页。

1575441082796

Flush List

LRU列中数据被修改后,产生脏页。数据库通过checkpoint机制将脏页刷新会磁盘,flush list中的页即为脏页列表。脏页即存在于LRU中,也存在于Flush中。LRU list用于管理缓冲池中页的可用性,Flush list用于将页刷新回磁盘。

重做日志缓存

首先将重做日志信息放入到缓冲区,然后按照一定频率将其刷新到重做日志文件,该文件默认大小为8M

触发缓冲到文件的时机:

  1. Master Thread每一秒将重做日志缓冲刷新到重做日志文件
  2. 每个事务提交时会将重做日志缓冲刷新到重做日志文件
  3. 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件

Checkpoint技术

保证持久性

事务数据库系统普遍都采用Write Ahead Log策略,当事务提交时,先写重做日志,再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复

当前数据库对 redo log 的设计都是循环使用的,为了防止被覆盖,必须强制 Checkpoint,将缓冲池中的页至少刷新到当前 redo log 的位置。

InnoDB 通过 Log Sequence Number, LSN 来标记版本,这是 8 字节的数字,每个页有 LSN,重做日志中也有 LSN,Checkpoint 也有 LSN,这个是联系三者的关键变量。

mysql> SHOW ENGINE INNODB STATUS\G
---
LOG
---
Log sequence number 293590838           LSN1事务创建时一条日志
Log flushed up to   293590838
Pages flushed up to 293590838
Last checkpoint at  293590829
0 pending log flushes, 0 pending chkp writes
1139 log i/o's done, 0.00 log i/o's/second

CheckPoint 机制

在 Innodb 每次都取最老的 modified page 对应的 LSN,并将此脏页的 LSN 作为Checkpoint 点记录到日志文件,意思就是 “此 LSN 之前对应的日志和数据都已经刷新到磁盘” 。

当 MySQL 启动做崩溃恢复时,会从 last checkpoint 对应的 LSN 开始扫描 redo log ,并将其应用到 buffer pool,直到 last checkpoint 对应的 LSN 等于 log flushed up to 对应的LSN,则恢复完成。

如下是整个 redo log 的生命周期。

1575445035088

  1. 创建阶段 (log sequence number, LSN1):事务创建一条日志,当前系统 LSN 最大值,新的事务日志 LSN 将在此基础上生成,也就是 LSN1+新日志的大小;
  2. 日志刷盘 (log flushed up to, LSN2):当前已经写入日志文件做持久化的 LSN;
  3. 数据刷盘 (oldest modified data log, LSN3):当前最旧的脏页数据对应的 LSN,写 Checkpoint 的时候直接将此 LSN 写入到日志文件;
  4. 写CKP (last checkpoint at, LSN4):当前已经写入 Checkpoint 的 LSN,也就是上次的写入;

对于系统来说,以上 4 个 LSN 是递减的,即: LSN1>=LSN2>=LSN3>=LSN4 。

InnoDB关键特性

  • 插入缓存
  • 两次写
  • 自适应哈希索引
  • 异步IO
  • 刷新邻接页

插入缓冲

问题:通过自增唯一主键(聚集索引)插入数据时,不需要磁盘的随机读取,但是当表中有多个非聚集的辅助索引,这个时候插入B+数就是离散的,为了解决随机访问磁盘导致的性能下降,就出现了Insert Buffer

对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在索引池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge操作

使用条件:

  • 索引时辅助索引
  • 索引不是唯一的

Insert Buffer的升级版:Change Buffer

对DML操作:Insert、Delete、Update都进行缓冲

具体实现

Insert Buffer的数据结构时一棵B+树,负责所有表辅助索引进行Insert Buffer

1575516155633

合并

等到合适的时机,会把Insert Buffer合并到索引树中

  • 辅助索引背督导缓冲池中
  • Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时
  • Master Thread

两次写

作用:数据页的可靠性保证

背景:从page写到磁盘,page大小为16kb,磁盘的每次IO为4kb,所以每页需要多次写入(partial page write),在途中如果宕机,则可能导致写入数据只成功了一部分

重做日志中记录的是对页的物理操作,如偏移量800,写"aaa"记录

1575523625821

  • 如果是写doublewrite buffer本身失败,那么这些数据不会被写到磁盘,InnoDB此时会从磁盘载入原始的数据,然后通过InnoDB的事务日志来计算出正确的数据,重新写入到doublewrite buffer。
  • 如果 doublewrite buffer写成功的话,但是写磁盘失败,InnoDB就不用通过事务日志来计算了,而是直接用buffer的数据再写一遍。
  • 在恢复的时候,InnoDB直接比较页面的checksum,如果不对的话,就从硬盘载入原始数据,再由事务日志开始推演出正确的数据。所以InnoDB的恢复通常需要较长的时间。

自适应哈希索引

InnoDB存储引擎会监控对表上各索引页的查询,如果观察到简历哈希索引可以带来速度提升,则建立哈希索引。

生成条件:

  • 访问模式一致
  • 以该模式访问了100次
  • 页通过该模式访问了N次,其中N=页中记录*1/16

参考

jin-yang.github.io/post/mysql-…

www.orczhou.com/index.php/2…

MySQL技术内幕(InnoDB存储引擎)