Mysql一条更新语句是则么执行的?

1.前言

        在上文,我们知道了在Mysqk中一条查询语句在底层是如何进行执行的,分别经过连接器、查询缓存、分析器、优化器、执行器最后到存储引擎。但是更新语句是则么执行的呢?实际上,更新语句也是要经过这个过程,只不过在更新的时候要清空所有查询缓存,这也就是为什么不推荐使用查询缓存。同时在更新的时候,会涉及到数据库中比较重要的三大日志分别是undo_log、redo_log 和binlog。

2.日志系统

        undo_log: InnoDB 存储引擎层生成的日志,实现了事务的原子性,主要用于事务回滚和MVCC(多版本并发控制)

        redo_log: InnoDB 存储引擎生成的日志,实现了事务的持久性,主要用于故障恢复(cache-safe)

        binlog: Mysql Server层生成的日志,主要用于归档和主从复制

2.1 undo_log

        我们来想一个这样的问题,如果一条DML语句开启了事务之后,在执行过程中如果还没有提交事务,这个时候Mysql崩溃了,那要则么恢复呢?

        那如果我们每次在事务执行过程中,都记录下回滚时所需要的信息到一个日志里,那么在事务执行过程中发生Mysql崩溃,就不需要担心数据丢失了,我们就可以通过这个日志来对数据进行恢复,这个日志就是我们常说的undo_log(回滚日志),它保证了事务ACID特性中的原子性

        undo_log是一种用于撤销回退的日志,在事务没有提交之前,Mysql会先记录更新前的数据到undo_log日志中,当事务需要回滚时,可以利用undo_log来进行回滚。实际undo_log+ReadView还能用来做MVCC(多版本并发控制)这个到后面讲。

        整个undo_log 工作流程大概如下

2.2 redo_log

        在执行Sql操作时,尤其时做更新的时候,如果该操作的记录在Buffer Pool缓冲区中,那么就操作相应的缓存页即可。如果不在就内存中就使用存储引擎从磁盘中读入记录到Buffer Pool缓冲区中,然后才能进行相应的更改,这样可以减少磁盘IO,提升读写性能。

        Buffer Pool是提高了读写效率这一点并没有错,但是问题就随之而来,Buffer Pool是基于内存的,众所周知,内存一旦断电就会出现丢失。那该则么办呢?这个时候redo_log就横空出世。

        为了防止数据丢失,当有一条记录需要更新的时候,InnoDB存储引擎就会先把记录写到redo_log中,然后在更新内存(同时标记为脏页),这个时候更新就算完成了,后续 InnoDB 引擎会在适当的时候,由后台线程先将 redo log 刷新到磁盘上,然后再将脏页落盘,这就是 Mysql 的 WAL 机制。

WAL(Write-Ahead Logging): 预写日志系统,其主要是指 Mysql 在执行写操作的时候并不是将脏页立刻更新到磁盘上,而是先记录在日志中,之后在合适的时间更新到磁盘中,Mysql 中的 redo log 就是采用了 WAL 机制。

redo执行流程如下图

2.3 binlog(归档日志)

        前面说的redo_log和undo_log都是由InnoDB存储引擎生成的,同时redo_log是Innodb独有的而binlog是都有。

        Mysql 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件。

        那么binlog和redo_log有啥区别呢?

        1. redo_log是InnoDB引擎所特有的;binlog是Mysql的Server层实现的,所有引擎都可以使用。

        2.redo_log是物理日志,记录的是“在某个数据页上做了什么修改”;而binlog是逻辑日志,记录的是这个语句的原始逻辑,比如 “给ID=2这一行的C字段添加1”。

        3.redo_log是顺序写,空间固定会用完;binlog是可以追加写入的,追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

3.更新流程

        好,那么现在我们来详细说一下一条更新语句是如何执行的

         假设现在是如下语句

update table set c=c+1 where id=2;

        1.首先执行器会先找到id=2这一行的数据。PS:如果Buffer Pool(内存)已经存在id这行数据那么就可以直接返回给执行器,否则就要先从磁盘中读入内存。

        2.执行器拿到这条数据后,给这个数据中字段C加上1,然后更新到内存中。

        3.同时在更新内存的时候,需要将更新操作记录到redo_log里面,此时redo_log处于prepare状态。然后告知执行器执行完成,随时可以提交事务。

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

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

        细心的同学已经看到这个在更新的流程中涉及到redo_log的prepare阶段和commit状态,那这个就是为什么呢?这就是接下来要讲的两阶段提交。

4.两阶段提交

        我们先来想个问题,如果不用两阶段提交会咋样?

        4.1 先写redo_log后写binlog

                假设这样一个场景如果redo_log写完了,binlog还没写完。这个时候我的Mysql崩溃了,然后我重启他,我可以用redo_log日志进行恢复,所以恢复完之后。C的值就成功+1了,对这没错。但是有一个问题,binlog我们通常来说是要做主从复制的,那么这个binlog没有更新成功我从节点不是还是旧的值吗!这样就会造成主从数据不一致的情况。

        4.2 先写binlog后写redo_log

                如果在写完binlog之后crash,由于redo_log还没写,那么Mysql挂了之后重启使用redo_log恢复数据,那么你这个C就是没有变的。那么一样的你binlog写完了你更新到从库那么从库值就变为了C+1则主从依然不同步。

 所以,从上面两个问题看出来,两阶段提交时非常重要的在写完redo_log的同时,给redo_log添加一个prepare的标志,同时在提交事务的时候要判断binlog是否已经写完,如果写完那么redo_log就会加上commit标志说明允许提交。

5. 参考

        极客学院Mysql 45讲

        Mysql更新语句的执行过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值