2.日志系统:一条SQL更新时怎样执行的?
1.前言
Mysql有很多有用的日志,例如错误日志、通用日志、二进制日志、重做日志等等。
2.更新流程
对于Mysql来说,更新一条sql语句,也需要先进行查询流程后再更新,因此,像前一节所说的一样,有一个查询流程,那么查询后它又是怎样更新的呢?
但是与查询流程不一样的是,更新流程还有涉及到两个重要的日志模块,一个是重做日志日志(redo log),另一个是归档日志(binlog),重做日志我就不细细说了,首先它是一个循环写的日志文件,大小比较固定,比如可以配置一组4个文件,每个文件的大小为1GB,这些都可以通过配置参数进行设置的,
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就会回到0号文件开头,checkpoint是当前要擦除的位置,也是往后推移并且循坏的,擦除记录前要把记录更新到数据文件。
- write pos和checkpoint之间的是’粉板‘上还空着的部分,可以用来记录新的操作。如果write pos 追上checkpoint,表示redolog日志满了,这时就不能再执行新的更新了,需要停下来先擦掉一些记录,把checkpoint推进一下。
有了redo log,innodb就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe.
然后再说说binlog, 首先,redo log是引擎层的日志,也是innodb引擎特有的日志,但是binlog日志是server层的日志,也就是说不管什么存储引擎,它都会有binlog日志。
binlog到底是什么?这个问题在我前面的文章应该有介绍,这里主要介绍一下这两种日志区别:
- redo log 是innodb引擎特有的,binlog是Mysql的server层实现的,所有引擎都可以使用。
- redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1”。
- redo log是循环写的,空间固定会用完,binlog日志是追加写入的,“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
因此,对于下满语句的更新流程:
mysql> update T set c=c+1 where ID=2;
- 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
流程图如下:
两阶段提交
为什么要有两阶段提交呢?就是为了让两份日志之间的逻辑一致,由于redolog和binlog是两个独立的逻辑,如果不用两阶段提交的话,比如说先写redolog,再写binlog日志,或者反正顺序执行,看看会出现什么情况。
- 先写 redo log 后写 binlog 假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1,但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
- 先写 binlog 后写 redo log 如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同
简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致