1. 前言
一条简简单单的更新操作涉及到太多的知识点了,首先我们要1.了解MySQL各个功能模块,然后在MySQL更新时不仅仅是操作表数据还要操作2.日志系统redo log、binlog和undo log,更新操作也不是实时更新到磁盘的而是通过3.Write-Ahead Logging机制先刷到内存再刷到磁盘,要刷到内存写日志的顺序又涉及到4.二阶段提交。如果数据涉及到索引还要对5.change buffer进行操作
2. MySQL日志系统
在MySQL中,有三种日志。分别是redo log、binlog和undo log。在更新过程中要对日志进行更新。
2.1 redo log(重做日志)
Write-Ahead Logging机制
在数据更新过程中,如果每次的更新操作都需要写进磁盘,磁盘也要找到对应的那条记录然后再更新,整个过程IO成本、查找成本都很高。MySQL使用WAL技术(全称Write-Ahead Logging)解决每次都要写磁盘的问题。WAL的关键点是先写日志再写磁盘。具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面并更新内存,InnoDB引擎会在适当的时候将这个操作记录更新到磁盘里面(这个操作往往是在系统比较空闲的时候做的)。
redo log的结构
InnoDB的redo log是固定大小的,比如可以配置一组4个文件,每个文件大小是1G。那么redo log总共可以记录4GB的操作,从头开始写,写到末尾又回到开头循环写。当写满时擦除一部分记录。存储的是物理逻辑(xxxx页修改了xxx)。
InnoDB的crash-safe能力
有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。保证ACID特性的持久性。当异常重启时,数据库根据磁盘数据和redo log对比保证数据正常。
2.2 binlog(归档日志)
为什么有两份日志?
binlog是Server层的日志,redo log是InnoDB引擎特有的日志。最开始MySQL里并没有InnoDB引擎,MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力, binlog日志只能用于归档。 而InnoDB是另一个公司以插件形式引入MySQL的, 既然只依靠binlog是没有crash-safe能力的, 所以InnoDB使用另外一套日志系统——也就是redo log来实现crash-safe能力。
redo log 和 binlog的区别
1、存储的内容
可以这样理解,binlog
记载的是update/delete/insert
这样的SQL语句,而redo log
记载的是物理修改的内容(xxxx页修改了xxx)。所以在搜索资料的时候也会有这样的说法:binlog
记录的是数据的逻辑变化,redo log
记录的是数据的物理变化
2、功能
redo log
的作用是实现持久化。数据库更新写完内存中的数据,如果数据库挂了,那我们可以通过redo log
来恢复内存还没来得及刷到磁盘的数据,将redo log
加载到内存里边,那内存就能恢复到挂掉之前的数据了。
binlog
的作用是进行复制和恢复。
- 主从服务器需要保持数据的一致性,通过
binlog
来同步数据。 - 如果整个数据库的数据都被删除了,
binlog
存储着所有的数据变更情况,那么可以通过binlog
来对数据进行恢复。
3、载体
redo log是InnoDB引擎特有的。binlog是MySQL的Server层实现的,所有引擎都可以使用。
4、记录方式
redo log是循环写的,空间固定会用完;binlog是追加写入的,一个文件写满后会切换到下一个文件而不会去覆盖以前的日志。
3.3 undo log
undo log
主要有两个作用:回滚和多版本并发控制(MVCC)
在数据修改的时候,不仅记录了redo log
,还记录undo log
,如果因为某些原因导致事务失败或回滚了,可以用undo log
进行回滚undo log
主要存储的也是逻辑日志,比如我们要insert
一条数据了,那undo log
会记录的一条对应的delete
日志。我们要update
一条记录时,它会记录一条对应相反的update记录。
回滚的实现就是找到undo log中对应的相反操作语句执行。
而多版本并发控制则是利用undo log做版本的回退(聊MVCCC时再具体讲)
3. 更新流程
首先是MySQL的各个功能模块,在MySQL查询过程会详细介绍。
以更新ID为2的行的某个值+1为例:
- 数据库连接,然后通过分析器进行词法分析和语法分析,再经过优化器选择索引等。
- **取数据:**执行器先找引擎取ID=2这一行。 ID是主键, 引擎直接用树搜索找到这一行。 如果ID=2这一行所在的数据页本来就在内存中, 就直接返回给执行器; 否则, 需要先从磁盘读入内存, 然后再返回。
- **更新数据:**执行器拿到引擎给的行数据, 把这个值加上1, 比如原来是N, 现在就是N+1, 得到新的一行数据, 再调用引擎接口写入这行新数据到内存(使用change buffer则不会马上更新到磁盘)。
- 将这个更新操作记录到redo log里面, 此时redo log处于prepare状态。 然后告知执行器执行完成了, 随时可以提交事务。
- 执行器生成这个操作的binlog, 并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口, 引擎把刚刚写入的redo log改成提交(commit) 状态, 更新完成。
4. 二阶段提交
上图执行过程中,redo log分为prepare阶段和commit阶段,在写入binlog的前后执行,这就是二阶段提交。
当commit 命令执行时,
- 写入redo log进入prepare阶段:事务进入commit prepare 阶段,事务中新生成的redo log 会被刷到磁盘
- 写入bin log:把binlog日志刷到磁盘
- redo log处于commit状态更新完成:innodb释放锁,清除undo信息,设置redo log提交状态。
4.1 为什么需要二阶段提交
由于存在redo log 和 binlog ,而他们两是相互独立的。而事务提交必须确保两者同时有效。不然会出现不一致的情形。我们对redo log和binlog不进行二阶段提交的顺序进行假设。
**先写redo log再写binlog:**redo log写了,binlog还没写,数据库崩了。通过redo log恢复数据库能将这条事务执行,但是binlog没有记录,从数据库就不能执行这条事务(或者在对数据库回到某个点时会没有这条事务),造成不一致的情况。
**先写binlog再写redo log:**binlog写了,redo log还没写,数据库崩了。通过redo log恢复数据库没有这条事务,但是binlog记录了,从数据库会执行这条事务(或者在对数据库回到某个点时会有这条事务的执行),造成不一致的情况。
4.2 二阶段提交怎么解决问题
上图的①时出现问题怎么解决?
这个时候redo log已经到磁盘了。binlog没有刷到磁盘所以会消失。服务器从故障中恢复时,读取磁盘中的redo log ,但是由于对应的redo log项还是prepare状态,就要判断binlog 是否完整,如果binlog完整则提交事务,如果binlog不完整则回滚事务。
上图的②时出现问题怎么解决?
这个时候redo log 和 binlog都已经存磁盘,服务器从redo log恢复就好了。
怎么判断binlog的完整性?
- statement 模式的 binlog,最后会有 COMMIT;
- row 模式的 binlog,最后会有一个 XID event
注:binlog有三种模式:statement、row和mixed(statement和row的混合)
找到redo log后怎么找到binlog?
redo log和binlog有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 redo log:如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
参考文章:
浅谈mysql的两阶段提交协议
MySQL实战45讲——丁奇