相比于查询语句执行流程,更新语句的执行也是差不多的,只不过多了两个写日志的操作,那就是bin log和redo log。
redo log(重做日志)
MySQL实战四十五讲里有一个非常的好的例子: 你是酒店老板,你有一块黑板专门记录赊账的客人名字和账目,当赊账的人多了黑板肯定记录不下这么多人,所以你还有一个专门记录的本子。本子就好比落到磁盘中的记录,黑板就好比redo log日志文件。如果有人要赊账或者还账的话你有两种选择:
- 把这个人从本子中的记录找到,然后把这次的帐加上去或者减下去
- 先把这个人的账目写到黑板上,然后等打烊或者空闲的时候再把黑板上的帐挪到本子里去
如果选择1的话,首先, 你得找到这个人的赊账总额那条记录。 你想想, 密密麻麻几十页, 要找到那个名字, 找到之后再拿出算盘计算, 最后再将结果写回到账本上。 相比之下, 还是2先在粉板上记一下方便。
MySQL就用到这种思想,称为write ahead logging(WAL),先写日志后写磁盘。
而且MySQL的redo log也是固定的,比如可以配置为一组四个文件,每个1G大小,那么redo log这块黑板就可以记录4G数据。从头开始写,写到末尾继续从开始写。(redo log是顺序写,速度很快。)
write pos是当前记录的位置, 一边写一边后移, 写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置, 也是往后推移并且循环的, 擦除记录前要把记录更新到数据文件。
write pos和checkpoint之间的是“粉板”上还空着的部分, 可以用来记录新的操作。 如果write pos追上checkpoint, 表示“粉板”满了, 这时候不能再执行新的更新, 得停下来先擦掉一些记录, 把checkpoint推进一下。
有了redo log, InnoDB就可以保证即使数据库发生异常重启, 之前提交的记录都不会丢失, 这个能力称为crash-safe。
要理解crash-safe这个概念, 可以想想我们前面赊账记录的例子。 只要赊账记录记在了粉板上或写在了账本上, 之后即使掌柜忘记了, 比如突然停业几天, 恢复生意后依然可以通过账本和粉板上的数据明确赊账账目 。
bin log(归档日志)
即二进制文件,用于记录用户对数据库更新的SQL语句信息,例如更改数据库表和更改内容的SQL语句都会记录到binlog里,但是对库表等内容的查询不会记录。
bin log 是MySQL server层所有的,所有存储引擎都可以使用。
更新执行流程
1.表
create table T(ID int primary key, c int);update T set c=c+1 where ID=2;
2.流程
- 向连接器进行连接
- 将查询缓存中的数据作废
- 词法分析会判断此语句为update语句
- 优化器决定要使用ID这个索引
- 执行器负责执行
- 之后进入细节:
1. 执行器先找引擎取ID=2这一行。 ID是主键, 引擎直接用树搜索找到这一行。 如果ID=2这一行所在的数据页本来就在内存中, 就直接返回给执行器; 否则, 需要先从磁盘读入内存, 然后再返回。
2. 执行器拿到引擎给的行数据, 把这个值加上1, 比如原来是N, 现在就是N+1, 得到新的一行数据, 再调用引擎接口写入这行新数据。
3. 引擎将这行新数据更新到内存中, 同时将这个更新操作记录到redo log里面, 此时redo log处于prepare状态。 然后告知执行器执行完成了, 随时可以提交事务。
4. 执行器生成这个操作的binlog, 并把binlog写入磁盘。
5. 执行器调用引擎的提交事务接口, 引擎把刚刚写入的redo log改成提交(commit) 状态, 更新完成。

