本篇将会用一条SQL语句来帮助我们初步的理解MySQL InnoDB存储引擎基本架构。我们先来看下面这条SQL语句:
update users set name = “xxx” where id = 10; |
---|
根据之前对于MySQL执行逻辑的了解,一条SQL指令被MySQL服务接受之后,会依次被SQL接口,解析器,优化器,执行器依次处理和调用。接着是由执行器负责执行优化器产生的执行计划,最终会调用InnoDB存储引擎去真正的执行。大致流程如下:
![]() |
---|
1.Buffer Pool
InnoDB存储引擎中有一个非常重要组件的内存组件,就是缓冲池(Buffer Pool)。缓冲池会缓存很多数据,以便后期在查询时,如果缓冲池中有数据就不需要去加载磁盘数据了。
当InnoDB存储引擎要执行更新语句时,比如要更新id=10这行数据,会先在缓冲池中查询是否有这行数据;如果不在会从磁盘中加载到缓冲池里,接着会对这行记录加独占锁。因为在这行数据的被修改时,不允许其他线程同时更新,所以需要要对这行记录加独占锁。关于MySQL锁的内容这里先不展开。
2.Undo Log
假设id=10这行数据的name列原来是”zhangsan”,现在要更新为”xxx”,此时MySQL第一步会先把要待更新的原来的值”zhangsan”和id=10这条数据相关的信息写入到Undo Log文件中去。
为什么要这样做呢?执行一条SQL指令是在一个事务里,在事务提交之前都是可以对数据进行回滚的。写Undo Log是为了未来可能要回滚数据的需要,把更新前的值写入日志文件中。如下图所示:
![]() |
---|
3.”脏数据”
写入Undo Log文件之后,接下来就可以更新这行记录了。更新时,会先更新缓冲池中的记录,此时这个数据就是”脏数据“了。之所以叫做”脏数据”是因为此时磁盘上id=10这条记录的name字段仍然是”zhangsan”,但在内存里这行数据已经被修改了,所以称之为”脏数据”。如下图所示:
![]() |
---|
4.Redo Log和Redo Log Buffer
【思考】内存缓冲池里的数据被修改了,但磁盘上的数据还没未被修改。此时如果MySQL宕机所在的机器宕机了,会导致内存缓冲池里修改过数据丢失,如何保障数据不丢失?
为了导致这种情况发生,MySQL会把对内存的修改写入到Redo Log Buffer中去。Redo Log Buffer是用来存放Redo Log的,它会记录当前对数据做了什么修改,比如:对id=10这条记录修改了name字段的值为”xxx”这条日志。如下图所示:
![]() |
---|
Redo Log作用是在MySQL宕机时,用来恢复更新过的数据。
【思考】如果在没有提交事务之前,MySOL崩溃,此时会导致内存缓冲池中修改过的数据丢失,同时写入Redo Log Buffer中的Red Log也会丢失,这种情况怎么办?
这种情况不要紧的,因为没提交事务,代表没执行成功,此时MySOL宕机虽然导致内存里的数据都丢失了,但是磁盘上的数据依然是原先数据,这是不影响的。
接下来如果想要提交一个事务,此时MySQL就会根据一定的策略把Redo Log从Redo Log Buffer中刷到磁盘文件中去。这个策略是通过参数innodb_flush_log_at_trx_commit配置的。具体策略如下:
选项 | 说明 | |
---|---|---|
(1) | 0 | 不会把Redo Log Buffer中的数据刷入磁盘文件 |
(2) | 1 | 提交事务时,把Redo Log Buffer中的数据刷入磁盘文件 |
(3) | 2 | 提交事务时,把Redo Log Buffer中的数据刷入磁盘文件对应的os cache中,可能过段时间才会把os cache中的数据写到磁盘文件里 |
下面分别对上述的三种情况在MySQL宕机时进行分析:
(1)innodb_flush_log_at_trx_commit = 0
MySQL宕机,内存中数据全部丢失导致数据丢失。
(2)innodb_flush_log_at_trx_commit = 1
如果此时提交事务后,MySQL宕机,被更改的数据已经被写入到Redo Log,Redo Log Buffer和缓冲池中的数据丢失,磁盘上的数据仍然是原先的数据,如下图这样的状态:
![]() |
---|
此时重启之后,MySQL会根据Redo Log去恢复之前做过的修改,如下图:
![]() |
---|
所以这种情况下不会丢失数据。
(3)innodb_flush_log_at_trx_commit = 2
当提交事务,Redo Log仅仅停留在os cache内存缓存里,数据没有进入磁盘文件。此时MySQL机器宕机,os cache里的Redo Log丢失,如下图所示:
![]() |
---|
综上分析,推荐innodb_flush_log_at_trx_commit设置为1,确保事务提交之后故障,不丢失数据。
5.binlog
Redo Log是一种偏向物理性质的重做日志,它记录了对哪个数据页中的什么记录做了个什么修改。另外Redo Log本身是属于MySQL InnoDB存储引擎。
另外MySQL还有一个日志叫做binlog,又称之为归档日志。它记录了偏向于逻辑性的日志,比如:对某个表中id=10的一行数据做了更新操作,更新以后的值是什么。另外,binlog不是MySQL InnoDB 存储引擎特有的日志文件,它属于MySQL Server自己的日志文件。
当提交事务时,会把Redo Log写入磁盘,同时还会把这次更新对应的binlog写入到磁盘文件中去,如下图所示:
![]() |
---|
在上图中可以分为两个阶段,步骤1-4是更新语句时做的;步骤5-6是从提交事务开始的,属于提交事务阶段。
对于binlog也有刷盘策略,是通过参数sync_binlog去进行控制的,策略如下:
选项 | 说明 | |
---|---|---|
(1) | 0(默认) | 将binlog刷入磁盘文件对应的os cache中,可能过段时间才会把os cache中的数据写到磁盘文件里 |
(2) | 1 | 提交事务时,强制把binlog中的数据刷入磁盘文件 |
当sync_binlog=0时,一旦宕机会导致数据丢失,如下图:
![]() |
---|
当sync_binlog=1时,当宕机不会导致数据丢失,如下图所示:
![]() |
---|
当binlog写入磁盘文件之后,接着会完成最终的事务提交。此时会把本次更新对应的binlog文件名称和这次更新的binlog日志在文件里的位置都写入到Redo Log中去,并且在Redo Log中写入一个commit标记,完成之后才算最终完成了事务的提交,如下图所示:
![]() |
---|
【思考】Redo Log中写入commit标记有什么意义?
这主要用来保持Redo Log与binlog一致的。在上图中,假设在提交事务时,步骤5-7这三个步骤必须都执行完毕才算是提交事务成功。
【分析】
(1)如果在完成步骤5时,即Redo Log刚刷入磁盘文件时,MySQL宕机会怎么办?
此时没有最终事务commit标记在Redo Log里,此次事务可以判定为不成功。不会导致Redo Log里有这次更新的日志,但是binlog里没有这次更新的日志,不会出现数据不一致的问题。
(2)如果要是完成步骤6时,即binlog写入磁盘时,MySQL宕机会怎么办?
因为没有Redo Log中的最终commit标记,此时事务提交也是失败的。必须是在Redo Log中写入最终的事务commit标记了,此时事务才算提交成功,而且Redo Log里有本次更新对应的日志, binlog里也有本次更新对应的日志,两者完全是一致。
【思考】提交事务之后,内存中Buffer Pool中的缓存数据更新了,同时磁盘里的Redo Log和binlog都记录了更行后的值。但是磁盘上的数据文件里的数据还没有更新。数据仍然是不一致的?
MySOL会有一个后台的IO线程在之后某个时间点会随机的把内存Buffer Pool中修改后的脏数据给刷回到磁盘上的数据文件里去,如下图所示:
![]() |
---|
如果IO线程把Buffer Pool中的”脏数据”刷在磁盘之前,MySQL宕机也不会影响。因为重启之后会根据Redo Log恢复之前提交事务做过的修改加载到内存中去,然后在未来的某个时间点再会随机的把内存Buffer Pool中修改后的脏数据给刷回到磁盘上的数据文件里去。