一条SQL更新语句是如何执行的
以下面语句为例
update T set c=c+1 where ID=2;
更新语句也会走查询语句的那一套流程,除此以外,更新语句还涉及两个重要的日志模块:redo log
重做日志和binlog
归档日志。
redo log
重做日志
在MySQL更新的时候,如果每次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录然后再更新,整个过程的IO
成本和查找成本都很高。MySQL使用了WAL
技术解决这个问题,全称是Write-Ahead Logging
,关键点是先写日志,再写磁盘。
当有一条记录需要更新的时候,InnoDB引擎会先把记录写到redo log
里,并更新内存,这个时候更新就算完成了。同时,InnoDB会在适当的时候,将这个操作记录更新到磁盘里,这个更新往往是系统比较空闲的时候进行。
InnoDB的redo log
是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么总共可以记录4GB的操作。如果满了的话,就需要先把一部分数据更新到磁盘里,再继续往下写。
如上图所示,write pos
是当前记录的位置,一边写一边往后移动。checkpoint
是当前要更新的位置,也是往后推移并循环的。write pos
和checkpoint
之间是还空闲的部分(注意是ib_logfile3-iblogfile0
那段),可以用来执行新的操作。如果write pos
追上了checkpoint
,就需要停下来先擦掉一些记录,把checkpoint
向前推进。
有了redo log
,InnoDB可以保证即使数据库异常重启,之前提交的记录也不会丢失,这个能力称为crash-safe
。
binlog
归档日志
redo log
重做日志是InnoDB引擎特有的日志,Server层的日志称为binlog
。
这两个日志的不同如下:
redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用
redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
- redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
执行器和InnoDB引擎在执行update语句时的内部流程如下:
- 执行器先找引擎取ID=2的这一行,由于ID是主键,所以引擎直接用树搜索找到这一行。如果ID=2这一行的数据页本来就在内存里,就直接返回给执行器;否则,先从磁盘里读入内存,然后在返回。
- 执行器拿到引擎给出的行数据后,把这个值+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存里,同时把更新操作记录到
redo log
里。此时redo log
处理prepare
状态。然后告知执行器执行完成了,随时可以提交事务。 - 执行器生成这个操作的
binlog
并写入磁盘 - 执行器调用引擎的提交事务接口,引擎把刚刚写入的
redo log
改为commit
状态,更新完成。
如下图所示,其中浅色框表示在InnoDB内部执行的,深色框表示在执行器中执行的。
两阶段提交
在上图里,最后三步将redo log
的写入拆分成了两个步骤:prepaer和commit
,这就是两阶段提交。
为什么必须要这么进行呢?这是为了让两份日志之间的逻辑一致。思考一个问题:怎么让数据库恢复到半个月内任意一秒的状态。