文章目录
- 1、更新过程
- 2、redo log重做日志(物理日志)
- 3、binlog归档日志(逻辑日志)
- 3.1、binlog与redo log区别
- 4、执行器和InnoDB引擎在执行update内部流程
- 5、恢复以及两阶段提交
- 5.1、恢复
- 5.2、为什么需要两阶段提交?
- 6、总结更新的具体过程
1、更新过程
- 建表语句:
mysql> create table T(ID int primary key, c int);
- 更新语句:
mysql> update T set c=c+1 where ID=2;
- 回顾执行select的过程:
- 首先还是进行连接数据库的操作,这块是由连接器执行。
- 因为是更新,所以查询缓存会失效,当有更新语句的时候,会把表T上所有的缓存结果清空。
- 然后就是分析器进行词法分析以及优化器决定使用ID这个索引。
- 但涉及到另外两个模块:
redo log(重做日志
)以及binlog(归档日志)
。
2、redo log重做日志(物理日志)
- redo log是
InnoDB引擎特有的日志
。 - WAL:write-Ahead Logging,关键点就是
先写日志
(也是先写磁盘,只是写日志是顺序写盘,速度很快),再写磁盘
。 - 具体:有一条记录需要更新的时候,InnoDB引擎就会
先把记录写到redo log里面
,并更新到内存
,此时算更新完成。同时,InnoDB引擎在合适的时候
,将这个操作记录更新到磁盘
。一般在系统比较空闲的时候做。 - InnoDB的
redo log是固定大小
。比如可以配置为一组4个文件,每个文件的大小为1GB,一共可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写
。
-
write pos是记录当前记录位置,边写边移动
,写到第三号文件末尾就回到0号文件开头。checkpoint是当前要擦除的位置,同样是边写边移动
,擦除的记录要更新到数据文件(在磁盘)
。 - 二者之间的空余部分,可以用来记录新的操作。如果write pos追上checkpoint,那么及时不能再执行新的更新,先擦除一些记录,让checkpoint后移。
- 那么
有了redo log,InnoDB可以保证即使发生异常重启,之前提交的记录都不会丢失
,这个能力成为crash-safe
。 -
crash-safe是按照redo log恢复内存中的状态
。
总结:redo log其实就是记录数据库还未写入到磁盘的状态,如果此时数据库系统发生异常,那么,数据库系统可以根据redo log恢复到发生异常时数据库在内存的状态。
redo log保证了数据库的持久性。
3、binlog归档日志(逻辑日志)
- MySQL分为
Server层(做MySQL功能层面的事)
和引擎层(负责存储相关的具体事宜)
。 -
binlog是Server层的日志
。 - binlog是记录所有数据库表结构变化以及表数据修改的二进制文件,主从数据库同步用到都是binlog。
- 两份日志:
-
最开始
MySQL没有InnoDB引擎,自带引擎是MyISAM
,但MyISAM没有crash-safe能力
,binlog只能用来归档。后来就有了redo log来实现crash-safe。
binlog从建库以来就有,只要是发生了增加、更新,binlog就会进行记录。
3.1、binlog与redo log区别
-
不同的存储引擎共享一个Server层
,那么binlog是所有引擎都可以使用的
。而redo log是InnoDB引擎独有的
。
2.redo log是物理日志,即:在哪个数据页上面做了什么改动
。binlog是逻辑日志,即:这个语句的原始逻辑
。上面的更新语句就是:给ID=2这一行的C字段+1。 -
redo log是循环写,空间固定会写完
。binlog是可以追加写的
,即:binlog文件写到一定大小之后会切换到下一个,并不会覆盖
之前的日志。
4、执行器和InnoDB引擎在执行update内部流程
- 按照之前分析,InnoDB会先找ID=2这一行。
- 因为ID是
主键
,那么引擎直接到主键索引树上搜索找到这一行
。如果ID=2这行所在的数据页本来就在内存中
,就直接返回给执行器
。否则
,要先
从磁盘读入到内存,再返回
。 - 执行器得到了
引擎给的行数据
,把这个值+1得到新的一行数据
,再调用引擎接口写入这行新数据
。 -
引擎将这行数据更新到内存中
,同时
将这个更新操作记录到redo log
,此时redo log处于prepare状态
。随后通知执行器执行完成
,随时可以提交事务
。 -
执行器生成这个操作的binlog
,并且将binlog写入到磁盘
。 - 执行器
调用引擎的提交事务接口
,引擎把刚刚写入的redo log改成提交状态
,更新完成
。
流程图:
5、恢复以及两阶段提交
5.1、恢复
- redo log的写入拆分成了两步:
- prepare
- commit
-
binlog会记录所有的逻辑操作,采用追加写的形式
。当DBA(数据库管理员)说半个月可以恢复,那么备份系统中一定会保存最近半个月的所有binlog
,同时系统会定期做整库备份,定期取决于系统的中套型,可以是一天一被备,也可以是一周一备份。 - 当需要
恢复
到指定的某一秒,场景:某天下午两点发现中午十二点有一次误删表,需要找回数据:
-
首先找到最近的一次全量备份
,运气好可能就是昨天晚上的一个备份,从这个备份恢复到临时库
。 -
从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻
。 - 这样
临时库就跟误删之前的线上库一样
,然后就可以把表数据从临时库取出来
,按需要恢复到线上库
。
5.2、为什么需要两阶段提交?
- .
两阶段提交的意义
?为了让两份日志之间的逻辑一致
。
采用反证法
。 - redo log和binlog是两个独立的逻辑,不用两阶段提交,要么就是先写完redo log再写binlog,要么就是先写binlog再写redo log。但是这样会出现问题。
- 假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中在写完第一个日志后,第二个日志还没有写完期间
发生了数据库崩溃
,可能出现的情况:
-
先写redo log再写binlog
。
即:写完了redo log,再写binlog的时候,MySQL进程异常重启,
由于redo log写完了,即使系统崩溃,也仍然能把数据恢复回来,所以恢复后这一行c的值为1
。
但是,由于binlog还没写完
,此时binlog里面就没有记录这个语句
。因此之后备份日志
的时候,存起来的binlog里面就没有这条语句
。
那么,如果需要这个binlog来恢复临时库的话,由于这个语句的binlog丢失
,那么临时库就会缺少这一次的更新,恢复出来的这一行c的值就为0,与原库不一致。
总结:因为redo log写好了,是可以进行恢复的,但是binlog没有写好,所以在此之后的备份日志记录的没有本次更新操作。如果之后想恢复,那么恢复的是更新之前的值,而不是更新之后的值。正确的应该是更新之后的值。
-
先写binlog再后redo log
。
即:写完binlog,在写redo log的时候,发生了崩溃。因为redo log没写,所以崩溃恢复以后这个
事务无效
,所以这一行的c值是0,但是binlog已经记录了“把c从0改为1”这个日志
,所以在之后用binlog来恢复的时候就多了一个事务出来
,恢复出来的这一行c的值就是1,与原库的值不一样(假设有redo log,此时恢复应该是0)。
总结:因为binlog写好了,所以记录的有本次更新操作记录,但是由于redo log并没有写好,恢复之后事务是无效的,那么值就是更新之前的,但由于binlog记录了更新操作,所以将值变为了更新之后的值。正确的应该是更新之前的值。
- 可以看见,不使用两阶段提交,
数据库的状态就有可能用和它的日志恢复出来的库的状态不一致
。 - 不只是误操作,当需要扩容的时候,即:需要再多搭建一些备库来增加系统的读能力的时候,常见做法就是用全量备份+应用binlog实现。
- redo log和binlog都可以用于表示事物的提交状态,而
两阶段提交就是让这两个状态保持逻辑上一致
。
6、总结更新的具体过程
- 开始和查询一样,连接器进行权限认证,分析器进行此法分析,优化器进行方案选择,执行器再进行一次权限检查,调用引擎接口。
- 不管是否有索引,引擎会查询到要修改的那一行数据,有缓存的话直接将该数据页返回给执行器,没有就先从磁盘中读到内存,再返回。
- 执行器得到从引擎返回的数据,然后进行相应的更新操作,又调用引擎接口写入新的行数据。引擎把数据保存在内存中,同时记录redo log,此时的redo log进行prepare状态。引擎通知执行器执行完毕,随时可以提交。
- 执行器收到这个通知之后,生成这个操作的binlog,且binlog写入到磁盘,再调用引擎提交事务的接口,随后引擎将redo log改为提交状态。
- 更新完成。