文章目录

  • 1、更新过程
  • 2、redo log重做日志(物理日志)
  • 3、binlog归档日志(逻辑日志)
  • 3.1、binlog与redo log区别
  • 4、执行器和InnoDB引擎在执行update内部流程
  • 5、恢复以及两阶段提交
  • 5.1、恢复
  • 5.2、为什么需要两阶段提交?
  • 6、总结更新的具体过程


学习来源

1、更新过程

  1. 建表语句:
mysql> create table T(ID int primary key, c int);
  1. 更新语句:
mysql> update T set c=c+1 where ID=2;
  1. 回顾执行select的过程:

mysql更新语句 REPLACE mysql更新语句立即生效_数据

  1. 首先还是进行连接数据库的操作,这块是由连接器执行。
  2. 因为是更新,所以查询缓存会失效,当有更新语句的时候,会把表T上所有的缓存结果清空。
  3. 然后就是分析器进行词法分析以及优化器决定使用ID这个索引。
  4. 但涉及到另外两个模块:redo log(重做日志)以及binlog(归档日志)

2、redo log重做日志(物理日志)

  1. redo log是InnoDB引擎特有的日志
  2. WAL:write-Ahead Logging,关键点就是先写日志(也是先写磁盘,只是写日志是顺序写盘,速度很快),再写磁盘
  3. 具体:有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面并更新到内存,此时算更新完成。同时,InnoDB引擎在合适的时候将这个操作记录更新到磁盘。一般在系统比较空闲的时候做。
  4. InnoDB的redo log是固定大小。比如可以配置为一组4个文件,每个文件的大小为1GB,一共可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写

mysql更新语句 REPLACE mysql更新语句立即生效_database_02

  1. write pos是记录当前记录位置,边写边移动,写到第三号文件末尾就回到0号文件开头。checkpoint是当前要擦除的位置,同样是边写边移动擦除的记录要更新到数据文件(在磁盘)
  2. 二者之间的空余部分,可以用来记录新的操作。如果write pos追上checkpoint,那么及时不能再执行新的更新,先擦除一些记录,让checkpoint后移。
  3. 那么有了redo log,InnoDB可以保证即使发生异常重启,之前提交的记录都不会丢失,这个能力成为crash-safe
  4. crash-safe是按照redo log恢复内存中的状态

总结:redo log其实就是记录数据库还未写入到磁盘的状态,如果此时数据库系统发生异常,那么,数据库系统可以根据redo log恢复到发生异常时数据库在内存的状态。
redo log保证了数据库的持久性。

3、binlog归档日志(逻辑日志)

  1. MySQL分为Server层(做MySQL功能层面的事)引擎层(负责存储相关的具体事宜)
  2. binlog是Server层的日志
  3. binlog是记录所有数据库表结构变化以及表数据修改的二进制文件,主从数据库同步用到都是binlog。
  4. 两份日志:
  1. 最开始MySQL没有InnoDB引擎,自带引擎是MyISAM,但MyISAM没有crash-safe能力,binlog只能用来归档。后来就有了redo log来实现crash-safe。

binlog从建库以来就有,只要是发生了增加、更新,binlog就会进行记录。

3.1、binlog与redo log区别

  1. 不同的存储引擎共享一个Server层,那么binlog是所有引擎都可以使用的。而redo log是InnoDB引擎独有的
    2.redo log是物理日志,即:在哪个数据页上面做了什么改动binlog是逻辑日志,即:这个语句的原始逻辑。上面的更新语句就是:给ID=2这一行的C字段+1。
  2. redo log是循环写,空间固定会写完binlog是可以追加写的,即:binlog文件写到一定大小之后会切换到下一个,并不会覆盖之前的日志。

4、执行器和InnoDB引擎在执行update内部流程

  1. 按照之前分析,InnoDB会先找ID=2这一行。
  2. 因为ID是主键,那么引擎直接到主键索引树上搜索找到这一行。如果ID=2这行所在的数据页本来就在内存中,就直接返回给执行器否则,要磁盘读入到内存,再返回
  3. 执行器得到了引擎给的行数据,把这个值+1得到新的一行数据,再调用引擎接口写入这行新数据
  4. 引擎将这行数据更新到内存中同时将这个更新操作记录到redo log,此时redo log处于prepare状态。随后通知执行器执行完成随时可以提交事务
  5. 执行器生成这个操作的binlog,并且将binlog写入到磁盘
  6. 执行器调用引擎的提交事务接口引擎把刚刚写入的redo log改成提交状态更新完成

流程图:

mysql更新语句 REPLACE mysql更新语句立即生效_mysql更新语句 REPLACE_03

5、恢复以及两阶段提交

5.1、恢复

  1. redo log的写入拆分成了两步:
  1. prepare
  2. commit
  1. binlog会记录所有的逻辑操作,采用追加写的形式。当DBA(数据库管理员)说半个月可以恢复,那么备份系统中一定会保存最近半个月的所有binlog,同时系统会定期做整库备份,定期取决于系统的中套型,可以是一天一被备,也可以是一周一备份。
  2. 当需要恢复到指定的某一秒,场景:某天下午两点发现中午十二点有一次误删表,需要找回数据:
  1. 首先找到最近的一次全量备份,运气好可能就是昨天晚上的一个备份,从这个备份恢复到临时库
  2. 从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻
  3. 这样临时库就跟误删之前的线上库一样,然后就可以把表数据从临时库取出来,按需要恢复到线上库

5.2、为什么需要两阶段提交?

  1. . 两阶段提交的意义为了让两份日志之间的逻辑一致
    采用反证法
  2. redo log和binlog是两个独立的逻辑,不用两阶段提交,要么就是先写完redo log再写binlog,要么就是先写binlog再写redo log。但是这样会出现问题。
  3. 假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了数据库崩溃,可能出现的情况:
  1. 先写redo log再写binlog

即:写完了redo log,再写binlog的时候,MySQL进程异常重启,由于redo log写完了,即使系统崩溃,也仍然能把数据恢复回来,所以恢复后这一行c的值为1
但是,由于binlog还没写完,此时binlog里面就没有记录这个语句。因此之后备份日志的时候,存起来的binlog里面就没有这条语句
那么,如果需要这个binlog来恢复临时库的话,由于这个语句的binlog丢失,那么临时库就会缺少这一次的更新,恢复出来的这一行c的值就为0,与原库不一致。
总结:因为redo log写好了,是可以进行恢复的,但是binlog没有写好,所以在此之后的备份日志记录的没有本次更新操作。如果之后想恢复,那么恢复的是更新之前的值,而不是更新之后的值。正确的应该是更新之后的值。

  1. 先写binlog再后redo log

即:写完binlog,在写redo log的时候,发生了崩溃。因为redo log没写,所以崩溃恢复以后这个事务无效,所以这一行的c值是0,但是binlog已经记录了“把c从0改为1”这个日志,所以在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不一样(假设有redo log,此时恢复应该是0)。
总结:因为binlog写好了,所以记录的有本次更新操作记录,但是由于redo log并没有写好,恢复之后事务是无效的,那么值就是更新之前的,但由于binlog记录了更新操作,所以将值变为了更新之后的值。正确的应该是更新之前的值。

  1. 可以看见,不使用两阶段提交,数据库的状态就有可能用和它的日志恢复出来的库的状态不一致
  2. 不只是误操作,当需要扩容的时候,即:需要再多搭建一些备库来增加系统的读能力的时候,常见做法就是用全量备份+应用binlog实现。
  3. redo log和binlog都可以用于表示事物的提交状态,而两阶段提交就是让这两个状态保持逻辑上一致

6、总结更新的具体过程

  1. 开始和查询一样,连接器进行权限认证,分析器进行此法分析,优化器进行方案选择,执行器再进行一次权限检查,调用引擎接口。
  2. 不管是否有索引,引擎会查询到要修改的那一行数据,有缓存的话直接将该数据页返回给执行器,没有就先从磁盘中读到内存,再返回
  3. 执行器得到从引擎返回的数据,然后进行相应的更新操作,又调用引擎接口写入新的行数据。引擎把数据保存在内存中,同时记录redo log,此时的redo log进行prepare状态。引擎通知执行器执行完毕,随时可以提交。
  4. 执行器收到这个通知之后,生成这个操作的binlog,且binlog写入到磁盘,再调用引擎提交事务的接口,随后引擎将redo log改为提交状态。
  5. 更新完成。