MySQL学习笔记(二)一条SQL更新语句

一条更新语句的执行流程 

从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键ID和一个整型字段c

mysql> create table T(ID int primary key, c int);

假如将ID=2这一行的值加1

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

MySQL学习笔记(二)一条SQL更新语句

执行语句要先通过连接器连接数据库。在一个表上更新的时候,跟这个表有关的查询缓存会失效。

接下来分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用ID这个索引。然后,执行器负责执行,找到这一行然后更新。

与查询不同,更新和涉及到两个重要的日志模块:redo log(重做日志)和binlog(归档日志)

redo log 重做日志

WAL技术(Write-Ahead Logging):先写日志,再写磁盘。因为如果每次都直接写磁盘的话会效率很低很慢,所以等不忙的时候再写入磁盘。

具体,就是当有一条记录需要更新的时候,InnoDB引擎会先把记录写到redo log里面,并更新内存。这个时候更新算完成。然后引擎会在适当的时候,再将这个操作记录更新到磁盘里,这时往往是系统比较空闲的时候。

但是redo log是固定的大小。有时候,假如内存太满,就必须先将一部分内存更新到磁盘,再将这部分擦掉,然后在内存中写入新的操作

MySQL学习笔记(二)一条SQL更新语句

假如说可以配置为一组4个文件,每个大小1G,那么总共可以记录4GB的操作。write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾就回到了0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并循环。

write pos和checkpoint之间是还空着的部分,可以记录新的操作。如果write pos追上checkpoint,这时代表redo log已满,得先擦掉一些记录,把checkpoint推进一下。

redo log可以使数据库即使发生异常重启,之前提交的记录也不会丢失,这称为crash-safe。

binlog 归档日志

redo log是InnoDB引擎特有的日志,而Server层自己的日志称为binlog,所有引擎都可使用。

redo log是物理日志,记录“在某数据页做了什么修改”。binlog是逻辑日志,记录这个语句的原始逻辑,“给ID=2这行的c字段加1”

redo log是循环写,空间固定会用完。binlog是追加写入,指文件写到一定大小会切换到下一个,并不会覆盖。

接下来是更新流程:

1.执行器先找到引擎取ID=2这一行。ID是主键,引擎通过树搜索找到这一行。如果这一行的数据页本来就在内存,就直接返回给执行器;否则需要先从磁盘读入内存,再返回。

2.执行器给这个值加1,再调用引擎接口写入这行新数据。

3.引擎将这行数据更新到内存,同时将这个更新操作记录到redo log。此时redo log会处于prepare状态,告知执行器执行完成,随时可提交。

4.执行器生成这个操作的binlog,并把binlog写入磁盘。

5.执行器调用引擎提交的事务接口,引擎把刚刚写入的redo log改为commit状态,更新完成。

MySQL学习笔记(二)一条SQL更新语句(浅色为InnoDB内部执行,深色表示执行器中执行)

redo log的写入在最后被binlog拆为两个步骤:prepare和commit,这就是两阶段提交。

两阶段提交

怎么让数据库恢复到半个月内任意一秒的状态?

这意味着备份系统中保存着最近半个月所有的binlog,同时系统会做定期的整库备份。可以一天一备,也可一周一备。

一天一备对比一周一备的好处是“最长恢复时间”更短。最坏的情况是需要应用一天的binlog。比如每天0点做全量备份,而要恢复出一个到昨晚23点的备份。一周一备最坏的情况就是用一周的binlog。

当然更频繁的全量备份需要消耗更多存储空间。需要根据业务重要性来评估。

比如发现某天下午两点发现中午十二点有一次误删表,需要找回数据,那可以:

首先找到最近一次全库备份,如果运气好就是昨晚一个备份,从这个备份恢复到临时库。

然后从备份的时间点开始,将备份的binlog依次取出,重放到中午误删那个时刻。

然后就可以将表数据从临时库取出来,按需要恢复到线上库。

假如不是两阶段:

1.先写redo log后写binlog。假设在redo log写完还没写binlog,也就是redo log写完即使崩溃,仍然能够把数据库恢复回来,这一行的c就是加完后的1。但是binlog没有写完,存起来的binlog就没有这条语句。如果后来用binlog恢复临时库,由于这句缺失,导致临时库恢复的这行就是0,与原库不同。

2.先写binlog后写redo log。假如binlog写完后crash,redo log还没写,崩溃后恢复这行c还是0。但是binlog里面已经记录了把c从0改为1这个日志,所以之后用binlog来恢复就多出一个事务,恢复的c就是1,与原库不同。

可以看到如果不是两阶段提交,那么数据库的状态可能就和用它的日志恢复出来的不一致。

两阶段提交就是让这两个状态保持逻辑上的一致。