MySQL为什么可以回滚

前面介绍了了一条SQL查询语句是如何执行的,其中查询语句经过连接器、分析器、优化器、执行器、搜索引擎完成一个全过程。那一条SQL更新语句是如何执行的呢?SQL更新语句比SQL查询过程多多涉及到两个日志模块:redo log(重做日志)和binlog(归档日志)。

redo log

在MySQL里面有一个问题,如果每一次更新操作都写进磁盘,然后磁盘再找到对应的那条记录,接着再更新,那么整个过程IO成本和查找成本都很高,为了解决这个问题,MySQL使用了 WAL(Write-Ahead Logging)技术,就是先写日志,再写磁盘。具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log日志里面,并更新内存,完成更新。同时,InnoDB引擎会在闲的时候(访问量较少的时间点)把操作记录更新到磁盘里面。

InnoDB的redo log日志的大小是固定的,假设大小是4G,分为4文件。那么就可以记录4G的操作记录。写的过程是从头开始写,写到末尾就又回到开头循环写。如下图所示:
blob.jpg
其中,write pos 是当前记录的位置,一边写一边后移,check point是当前要擦出的位置,也是往后移。

有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

binlog

MySQL包括server层和存储引擎层。上面提到的redo log是InnoDB引擎特有的日志,binlog(归档日志)则是Server层自己的日志。binlog日志只能用于归档,没有crash-safe能力,所以MySQL里面会出现两种日志。其区别如下:

  • redo log是InnoDB引擎特有的,binlog日志MySQL的Server层实现的,所有引擎都可以使用;
  • redo log是物理日志,记录的是在在某个数据页做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑,有两种记法:1,statement格式记SQL语句;2,row格式记录行的内容,记两条,更新前和更新后。
  • redo log是循环写的,空间会用完;binlog是追加写,不会覆盖以前的日志。

下面给出update(update Talbe set c=c+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)状态,更新完成。
    下图是update语句执行的流程图,浅色框表示在InnoDB内执行,深色框表示在执行器中执行。
    blob.jpg

总结

前面的两个日志就可以解释MySQL为什么可以回滚。在MySQL中可以这样设置参数:
redo log用于保证 crash-safe的能力。 innodb_flush_log_at_trx_commit 参数设置为1,表示每次事务的redo log都直接持久化到磁盘。

sync_binlog这个参数也设置成1,表示每次事务的binlog都持久化到磁盘上。

日志系统的两阶段提交是保持一致性的重要手段,事务中也一直在用这个。

参考资料:林晓斌老师的MySQL实战45讲

发表评论

电子邮件地址不会被公开。

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部