Mysql专栏(二)Innodb数据写入过程

Mysql Innodb数据写入总览

Innodb结构图

以下内容参考自Mysql官方文档
Mysql专栏(二)Innodb数据写入过程_第1张图片

Innodb数据写入过程(开启binlog)

Mysql专栏(二)Innodb数据写入过程_第2张图片
由于innodb需要事务性的保证(redo log、undo log),所以写入流程会复杂一些。
首先,在数据要被写入或者修改时,一定要先查找到该数据所位于的page(Mysql操控数据的最小单位),如果page没有位于buffer pool,会发生缺页中断,加载磁盘上的page到buffer pool中。
查找到page以后,先要保存当前数据到undo log日志中(为了事务回滚后,数据也能回滚)。
之后会直接修改page上的数据(buffer pool中的脏页达到一定比例时,会进行刷盘操作),并记录在redo log buffer中记录redo log日志(用于事务的持久化)。
如果数据库同时开启了binlog,也会触发一个rego log和binlog的二阶段提交过程。

以上是正常流程,Mysql对于索引上的写操作进行了额外优化,详情见change buffer

Innodb相关组件介绍

redolog

redolog日志是Innodb用于保障事务中持久性的基石。
为了提升数据的写入速度,Mysql只是简单的在Page buffer(内存中)修改了数据,并没有进行刷盘操作,在某些情况下,数据会发生丢失的可能。所以为了避免数据丢失,Mysql使用了WAL机制,在事务数据写入内存后,会紧接着写入redo日志,这样在意外宕机的情况下,Mysql仍能根据redo日志恢复出完整数据。
redo默认是先写入log buffer中的,所以会存在redo丢失的可能,Mysql提供了innodb_flush_log_at_trx_commit参数用于控制刷盘时机。
innodb_flush_log_at_trx_commit = 0 :只写log buffer,redo log buffer每隔1S会刷到磁盘上。
innodb_flush_log_at_trx_commit = 1 :每次事务提交前都会添加到redo log日志并刷到磁盘上。
innodb_flush_log_at_trx_commit = 2 :每次事务提交前只会写入redo log日志但不会刷盘,会等待每秒一次的刷盘操作。
刷盘是个昂贵操作,但是为了保障数据不丢失,一般都会设置成1,所以Mysql写入性能并不是很强。

redolog、binlog双写一致性

有关双写一致性参考于官方文档
binlog是在Mysql层面上的日志,而redolog只是Innodb为了持久性而设计的日志,一般情况下,为了做数据同步用,binlog都会是开启状态,Innodb采用了两阶段提交方式来保障redolog和binlog的一致性。

  1. 先写redolog日志,生成事务标记xid,并设置状态为prepare状态
  2. 写入binlog日志
  3. 进行事务提交,更改xid的redolog变成commit状态
    3.1. 假设事务提交失败了,但是binlog写入成功了,Mysql会从binlog日志中获取最后一条xid,并将该xid对应的redolog改为提交状态并用于恢复数据。

Mysql5.7以后默认开启了两阶段提交并且不可关闭,但是这会带来额外的消耗,因为想要保证强一致性的话,redo日志会被刷盘两次(prepare和commit)

undolog

undolog会保留每行数据一段时间内所有事务的操作记录,用于进行事务回滚以及帮助MVCC进行事务隔离。
由于undo链过长会影响性能,所以Mysql会定时的获取当前所有readview中最老的活跃事务id,并使用purge线程将该事务之前的undolog进行回收。

buffer pool

buffer pool是Mysql最核心的一块区域,主要作用就是为了提升Mysql的读写性能而存在的。
Mysql在修改数据时,并不会直接去修改磁盘上的数据(属于随机IO,性能很差),而是先将磁盘上的page加载到buffer pool再进行操作,相当于将磁盘的随机IO转变成了内存的随机IO,提升了大量性能。
buffer pool使用变种LRU算法进行page的维护和淘汰策略,如下图所示。
Mysql专栏(二)Innodb数据写入过程_第3张图片
该算法将buffer pool按照5:3分成了两部分,page第一次被加载时,会先进入old sublist区域的头部。
在old区域中的page会等待再次读取并且停留时间达到一定目标后,才会进入到new sublist区域中。
而new区域中的page也会随时间推移,而降级到old区域中。
当buffer pool空间不足时,会先从old区域的尾部进行淘汰。

change buffer

如果一条update语句是位于普通索引(非唯一索引)上的修改,比方说要修改page500上的某条数据,那么该条修改记录会先记录到change buffer中,而不是产生缺页中断去磁盘上加载新page500。如果在之后的过程中,有新的查询需要加载该page500,当page500加载到buffer pool时,会与change buffer中的相关记录做一次合并操作。
这样可以看出明显能节省一次IO过程,有利于写性能的提高。

double write

Mysql是以page为单位从磁盘上获取数据加载到内存或者从内存中写入磁盘的,page的默认大小为16KB,而目前并不是所有的磁盘都能支持16KB的原子性写入的(很多磁盘的块大小是4KB),所以会导致一种情况,Mysql向磁盘中写入一个page时,如果发生意外,磁盘上就会存在一个损坏的page,这将导致Mysql在下次重启过程中,无法恢复该页数据,导致数据丢失。
所以buffer pool中的脏页在写入磁盘前,会先写入double write buffer(会持久化到共享表空间),这样在磁盘上的page发生缺失时,还可以根据double write中的副本页进行恢复。
如果double write持久化时Mysql挂了,那么Mysql会根据脏页前的page + redo log 进行数据恢复。
在innodb中double write是默认开启的,如果不在乎数据丢失或者磁盘可以支持16KB原子写,可以关闭该功能,用于提高Mysql写入性能。

你可能感兴趣的:(Mysql专栏,mysql,数据库)