一、更新涉及log
与SELECT流程不同,UPDATE操作会涉及两个日志模块,分别是redo log和bin log。
A、redo log
如果每次UPDATE操作,都找到对应记录,然后更新写入磁盘,IO成本、查找成本都很高。为了解决这个问题,MySQL设计者使用了WAL(Write-Ahead Logging)技术,先写日志,再写磁盘。
当有一条记录需要更新时,InnoDB引擎会把记录写到redo log里,并更新内存,并认为此时更新完成;InnoDB会在适当时,将记录更新到磁盘。
但是redo log大小固定,配置为一组4个文件。write_pos是当前记录的位置,checkpoint是需要擦除的位置,擦除前将记录更新到数据文件。相当于一个循环队列,如果队列写满,需要先暂定,将checkpoint向前推荐一段空间,再进行写入。
有了redo log,InnoDB可以保证数据库发生异常重启,记录不丢失,称为crash-safe。
B、bin log
redo log是InnoDB存储引擎的日志,Server层的日志为bin log。redo log是循环写入,bin log是追加写入,不会覆盖之前的日志。
二、UPDATE流程
mysql> UPDATE T SET age = age+1 WHERE id = 1;执行器先找引擎找id=1这行记录。id为主键,引擎通过索引找到记录;
执行器拿到行记录,对age+1,调用引擎写接口,更新数据;
引擎将新记录更新到内存,并将更新记录写入redo log,redo log处于prepare状态,随时可以提交事务;
执行器生成bin log写入磁盘;
执行器调用引擎提交事务接口,把redo log成成commit状态,更新完成。
innodb_flush_log_at_trx_commit设置为1,表示每次事务的redo log都直接持久化到磁盘,可以保证MySQL异常重启之后数据不丢失。
sync_binlog设置为1,表示每次事务的bin log都持久化到磁盘,可以保证MySQL异常重启之后bin log不丢失。
三、二阶段提交
为了让两份日志逻辑一致,上述操作使用了二阶段提交。
假设不使用二阶段提交,会有如下两种情况。
假设age = 10,使用之前的UPDATE SQL。
A.先写redo log,后写bin log;中间发生崩溃,通过redo log自动恢复数据,age = 11。但是因为bin log没有写入,如果用之前的数据进行恢复,age = 10。
B.先写bin log,后写redo log;中间发生崩溃,age = 10(未更新),使用bin log恢复,age = 11。
都会出现数据不一致的情况,使用二阶段提交,让两份日志数据保持逻辑上的一致。
参考: