InnoDB存储引擎内存模型与磁盘同步
InnoDB数据结构如下:
1. 内存与磁盘数据交互机制
innoDB在内存跟磁盘之间是以page页为单位来交互的,因为如果用行交互,那么假如我查询n条数据,需要跟磁盘交互n次,但是page页可能会很少,也不会用extent区来交互,因为一个extent包含64个页,加载64个页到内存,会导致内存浪费。所以,基于内存的利用率与性能考虑,InnoDB选择了page页
2. 预读机制
预先把在查询过程中使用到的数据读到内存中,减少磁盘 I/O 操作。通过顺序预读,InnoDB 存储引擎可以利用自然的数据存储顺序,提高顺序扫描的效率,进而提高查询性能。
- 线性预读:
按照访问顺序的页来执行预加载 某个区里面的页面有多少个页按顺序访问了,那么就会预加载这个区里面所有的页。
具体多少个页被顺序访问,具体配置为innodb_read_ahead_threshold
-- 默认为56 可以配置0-64,因为一个extent为64个页
SELECT @@innodb_read_ahead_threshold;
- 随机预读:
根据缓存池中已有的页来预加载,如果在缓冲池中找到了来自同一个区连续的13个页面,InnoDB会异步发出一个请求来预取该区剩余的页面。
通过开关开关控制,默认关闭:
SELECT @@innodb_random_read_ahead;
3. 数据同步机制
为了提高效率,innodb每次更改数据,会去先判断内存是否存在,如果数据所在的page页存在内存,会直接更改内存中page的数据。那么在我们的系统中,就有2份数据,一份在内存,一份在磁盘,所以就需要数据同步,保证一致性。
- 脏页
这个在内存中改动过的、还没有同步到磁盘的页就是脏页。InnoDB的bufferpool中,将脏页信息单独放在一个链表,刷脏的时候,只需要从这个链表找就可以了,这个链表就叫做Flush链表。 - 异步刷脏
那么这些脏页怎么同步到磁盘的?是不是每次更改一条数据,我就把这个数据的page页同步到磁盘?假如每次操作都需要先跟磁盘同步,我们发现有以下几个问题:- 由于内存跟磁盘交互的最小单位是page页,那么你改动一行数据,整个页都需要跟磁盘进行交互同步。
- 这个更改的数据你是不知道在哪个磁盘位置的,属于一个随机IO。如果每次都先保证同步到磁盘,那么操作数据会非常非常非常慢。
所以,采用的是异步刷盘机制。
刷脏是由异步线程去做的,具体的线程数由配置innodb_page_cleaners控制
SELECT @@innodb_page_cleaners; -- 默认是4 但是不能超过buffer-pool的实例数
SELECT @@innodb_buffer_pool_instances;
并且通过以下2个参数来控制我内存中的脏页量
SELECT @@innodb_max_dirty_pages_pct_lwm; -- 当脏页数量低于特定阈值时InnoDB存储引擎开始刷新脏页的行为,默认10
SELECT @@innodb_max_dirty_pages_pct; -- 内存中可以存在的脏页的最大百分比
- 双写缓存区
page页会异步刷新到磁盘,但是page页的大小是16k,而操作系统 的是4K,一个页是需要多次同步到磁盘的,中间如果出现操作系统,磁盘或者进程意外退出怎么办?
所以Mysql提供了一个doubleWrite机制,就是page页刷新到磁盘的时候,把这个page数据写到不同的地方去,当出现问题时,由备份来达到持久性跟数据的一致性。
4. RedoLog
数据是异步刷新到磁盘的,那么假如,在同步到磁盘之前就宕机了。数据就丢失了,那么InnoDB怎么去保证数据的一致性与持久性呢?所以又引入了RedoLog日志。RedoLog又称作重做日志,当我发生异常情况,导致数据丢失的时候,我可以从我的RedoLog日志中找到我想要的数据。
Redolog记录的大概结构:
type:操作类型 插入、修改还是删除
spaceId:表空间ID
page numbwe: 所在的页
data: 修改的前后数据
RedoLog的目的,是去保证我的数据页在内存,但是还没有同步到磁盘的时候,宕机导致的数据丢失。所以在sql语句提交之前,肯定会保存改动的Redolog到redolog file文件。当改动数据还在内存,没有同步到磁盘就宕机的时候,会通过RedoLog文件里面找到改动点,进行同步到磁盘。RedoLogFile的大小是固定的,写入的日志数量是有限制的,并且它的目的只是去保证数据不丢失,数据落盘了,这些日志就没有用了,所以RedoLog采用的是循环覆盖写的方式。当RedoLog空间的大小满了的时候,会对以前数据进行覆盖。为了保证被覆盖的数据不会丢失,在Redolog快满的时候,也会去进行刷脏,刷脏后,对应的Redolog日志就无效可以被覆盖,所以RedoLog的写是循环写的方式。
为啥不直接实时数据同步到磁盘,而是要写入一个RedoLog文件呢?
1.因为bufferPool跟磁盘交互的最小单位是page,所以,只要page里面改动一条数据,整个page都会进行跟磁盘同步,导致不必要的同步。RedoLog只会同步某些记录。
2.你改动的数据是随机的,不是顺序的,随机IO的性能比较慢,但是RedoLog是一直往上加,是顺序IO,速度比数据page同步要快。
为了保证数据一致性跟持久性的同时,性能得到保证,innodb内存中又申请了一个LogBuffer区间,这个内存区间用来缓存我们的RedoLog, RedoLog不马上写到磁盘,而是先写LogBuffer,然后再从Log Buffer同步到磁盘。
RedoLog同步方案:
SELECT @@innodb_flush_log_at_trx_commit; //RedoLog同步方案默认设置为1
1: 每次事务提交时,将日志刷新到磁盘,安全性高,能够保证持久性,默认配置
0: 每秒从内存写到操作系统,并且刷新(fsync())到硬盘,可能会导致数据丢失
2:每次写入logbuffer 并且写到操作系统,但是每秒fsync()到磁盘,最终刷新交给操作系统操作,只要操作系统不挂,也能保证持久性,但是操作系统挂了,数据没刷新就会数据丢失