一条更新SQL执行时,和查询SQL一样,要经过连接器、分析器、优化器、执行器等过程。不同的是,更新流程还会涉及到两个重要的日志模块:redo log和binlog。

redo log

如果每次的更新操作都要写进磁盘的话,需要在磁盘中找到对应的记录,然后更新。整个过程的IO成本、查询成本都很高。为了解决这个问题,MySQL采用了WAL技术,也就是Write-Ahead Logging,关键点就是先写日志,找一个合适的时间再写磁盘。
具体的就是,当一条记录需要更新时,InnoDB引擎会先把记录写到redo log中,并更新内存,这个时候更新就算完成了。同时InnoDB会在适当的时候,将这个操作记录更新到磁盘中,这个磁盘更新操作往往是在系统比较空闲的时候进行。InnoDB中的redo log是大小固定的,比如可以配置为4个文件,每个文件大小1GB,也就是总共4GB大小。如果redo log写满了,这个时候就不能再执行新的更新了,需要停下来先擦掉一些记录。
write pos代表当前记录的位置,随着记录写入向后推进,写到文件末尾后就会回到文件开头。check point是当前要擦除的开始位置,也是向后推进并循环的。write pos和check point之间代表文件中空闲的部分,如果write pos追上了check point,代表文件写满了,则需要擦掉一些记录更新到磁盘中,并把check point向后推进一部分。
有了redo log,InnoDB就可以保证,即使数据库发生异常重启,之前一段时间内提交的记录都不会丢失,这个能力称为crash-safe。通过将innodb_flush_log_at_trx_commit参数设置成1,表示每次事务的redo log都直接持久化到磁盘,保证MySQL异常重启后数据不丢失。

binlog

之前了解过,MySQL整体看分为两块,一块是server层,负责MySQL功能层面的事,另一块是引擎层,负责数据存储的事。redo log是InnoDB引擎特有的日志文件,而server层也有自己的日志文件,也就是binlog。
为什么MySQL会有两份日志文件?
因为最开始MySQL并没有InnoDB引擎,开始MySQL自带的引擎是MyISAM,MyISAM没有crash-safe能力,server层的binlog日志只能用来归档。InnoDB引擎是另一个公司以插件的形式引入到MySQL中,由于只依靠server层的binlog是无法保证crash-safe的,所以InnoDB引擎使用了另一套日志系统,也就是redo log来保证crash-safe能力。
binlog和redo log的不同:
1)binlog是server层实现的,所有引擎都可以使用。redo log是InnoDB引擎特有的。
2)redo log是物理日志,记录的是‘在某个数据页上做了什么修改’;binlog是逻辑日志,记录的是语句的原始逻辑,比如‘给id =2这一行的age字段加1’。
3)redo log日志是循环写的,空间固定,用完后会覆盖。binlog是追加写入的,写到一定大小后会切换到下一个binlog文件,不会覆盖以前的日志。
通过将sync_binlog参数设置成1,表示每次事务的binlog都持久化到磁盘,保证MySQL异常重启后binlog不会丢失。

InnoDB引擎执行简单update时的流程

update table set age = age+1 where id=2;
1)执行器先找引擎取id=2这一行,id是主键,引擎直接用树搜索找到这一行。如果id=2这一行所在的数据页本来就在内存中,则将数据直接返回给执行器;否则,需要先从磁盘读入内存,然后返回给执行器。
2)执行器得到了引擎返回的数据,把这个值加上1,得到一行新的数据,再调用引擎的接口写入这行新数据。
3)引擎将这行新数据更新到内存中,同时将这个更新操作记录在redo log里面,此时redo log处于prepare状态。然后告诉执行器执行完成了,随时可以提交事务。
4)执行器生成这个操作的binlog,并把binlog写入磁盘。
5)执行器调用引擎的提交事务接口,引擎把刚写入的redo log改成commit状态,更新完成。

两阶段提交

redo log的写入分为了prepare、commit两步,这就是两阶段提交。
为什么需要两阶段提交?
因为redo log和binlog是两个独立的文件,两阶段提交的目的是保证两个文件有效的状态保持一致。如果不是两阶段提交,那就是要么先写完redo log,后写binlog;要么就是先写完binlog,后写redo log。
如果redo log有效而binlog无效:则原主库有本次更新的记录,而通过binlog同步内容的从库没有该条记录。
如果binlog有效而redo log无效:则从库有该条记录,而主库没有该条记录。

时间点1——prepare——时间点2——commit——时间点3
如果时间点1出现问题,这时redo log和binlog都没有有效记录,相当于事务回滚了,数据一致。
如果时间点2出现问题,这时该事务在redo log是prepare状态,对于恢复时处于prepare尚未commit状态的redo log,需要去磁盘中已保存的binlog中检查该事务是否完整。如果binlog中该事务完整,则在恢复时提交该事务,不完整则回滚该事务。判断binlog是否完整:对于statement格式,完整的事务最后有commit;对于row格式,完整的事务会记录XID event。
如果时间点3出现问题,这时该事务在redo log和binlog都是有效状态,服务器从redo log恢复即可。