一条更新语句会怎么执行呢?

mysql> update T set c=c+1 where ID=2;

按照MySQL的基础架构,在执行语句前,需要连接数据库。因为这是一条更新操作,如果有查询缓存,则全把与该表相关的缓存清除(这也是为什么不建议使用缓存)。经过分析器进行词法分析语法分析可知,这是一条更新语句,语法正确。再经过优化器判断,使用ID作为索引查询,执行器负责具体执行,找到该行,并更新数据。
0d2070e8f84c4801adbfa03bda1f98d9.png

但与查询过程不同的是,它还涉及两个重要的日志模块:redo logbinlog

redo log

看英文名字,就是“重做日志”嘛。具体地说,当有一条记录需要更新时,InnoDB引擎会先把记录写到redo log中,并更新内存,这时更新完成。在适当的时候,将这个操作记录更新到磁盘中。

这样做有个好处,首先,不用每次更新都写入磁盘,因为每次更新操作需要在磁盘中找到那条记录,然后再更新,这个IO成本、查找成本都很高,这样做可以提高效率。另外,这个记录redo log是在系统比较空闲的时候做,对正常使用影响较少。

不过,redo log的大小是固定的,比如可以像下图一样配置。

16a7950217b3f0f4ed02db5db59562a7.png
write pos是当前记录的位置,check point是当前要擦除的位置。它们都是边写边后移的但是checkpoint在擦除前需要把记录更新。

write pos和check point之间的部分就是空的部分,可以记录新操作。

有了redo log,InnoDB可以保证即使在数据库发生重启,之前提交的记录都不会消失,这种能力叫做crash-safe

binlog

这个日志被称为归档日志。

从MySQL整体看,它分为Server层和引擎层。刚刚介绍的redo log是InnoDB独有的日志,而binlog是Server层的日志

最开始时,MySQL的默认引擎是MyISAM,它是没有crash-safe能力的,因为它没有redo log,只有binlog,而binlog只能用于归档。

说说它们间的不同:

  1. redo log是InnoDB独有的,而binlog是MySQL的Server层实现,所有引擎都能使用
  2. redo log是物理日志,记录的是在某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给id=1这行的a加1”.
  3. redo log是循环写的,空间固定会用完;binlog是追加写的,写到一定大小后会切换到下一个,并不会覆盖以前的日志。

看回最开始那条更新语句,它的执行流程是怎样的呢?(重点注意日志的变化!)

  1. 执行器先找引擎取ID=2这行。因为ID是主键,所以引擎直接用树搜索找到一行。若这行所在的数据页在内存,直接返回给执行器;若不在,则从磁盘取入内存,再返回。
  2. 执行器拿到引擎取到的行数据,把这个值+1,得到新的一行数据,再调用引擎写回这行新数据。
  3. 引擎将这行新数据更新到内存中,同时更新操作记录到redo log中。此时redo log处理prepare状态,然后告知执行器完成了,可以随时提交事务。
  4. 执行器生成这个操作的biglog,并写入磁盘
  5. 执行器调用引擎的提交事务接口,把引擎刚刚写入的redo log改成提交(commit)状态,更新完成。

2e5bff4910ec189fe1ee6e2ecc7b4bbe.png

两阶段提交

从刚刚的流程会发现,redo log的状态变化:prepare-->commit,这是为了保持两份日志的逻辑一致。

其实,这个做法还是可以保证系统可以恢复到某个时间的任意一秒的状态。

举个例子:比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:

  • 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
  • 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。

若redo log和bin log是两个独立的逻辑,会怎样呢?

  1. 先写redo log再写binlog。假设redo log写完,binlog还没写完,意外崩溃了,会发现binlog里面没有这条语句,如果用binlog恢复库的话,由于这个语句的binlog丢失,恢复出来的数据就少了那次更新,与原库值不同。
  2. 先写binlog再写redo log。如果binlog写完之后crash,由于redo log没写,它在崩溃恢复后这个事务无效,所以这次更新的值还是原来的值。但是binlog中记录了更新的值。所以,在之后用binlog恢复时就会多一个事务出来,恢复出来的这一行值与原库不同。

由此可见,两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,即使你不做数据库内核开发,日常开发中也有可能会用到。

一些记录

binlog用于归档,它才是老大嘛,而redo log是InnoDB专有的,它是为了辅助binlog的。若commit之前挂掉,恢复时,没有完成的事务不会回滚,这也不会出现上面的问题。