上一篇我们的mysql查询语句执行过程我们侧重点与mysql的server的介绍,这次我们从mysql的update语句的执行流程了解下引擎层的执行过程。
Mysql一条update语句的更新流程

连接验证及解析
这里的流程跟select语句的执行流程一致,具体流程可以查看另外一篇的博文:从查询sql了解MySQL的执行过程
客户端与服务层建立连接,语句通过解析器和预处理后抛给优化器进行处理,然后调用引擎层的接口执行具体的语句(以innodb 引擎为例)
引擎层执行流程
引擎层执行流程图

我们重点关注引擎层执行的流程,其中包含了写多个日志文件,其中日志文件的介绍与使用可以查看另外一篇博文:细说MySQL的日志
写重做日志
innodb 引擎首先开启事务,对旧数据生成一个UPDATE的语句(如果是INSERT会生成UPDATE语句),用于提交失败后回滚,写入undo log,得到回滚指针,并且更新这个数据行的回滚指针和版本号(会设置为更新的事务id)。
从索引中查找数据
如果i查询的数据页本来就在内存 中,就直接返回给执行器更新;
如果记录不在内存,接下来会判断索引是否是唯一索引;
如果不是唯一索引,InnoDB会将更新操作缓存在change buffer中;
如果是唯一索引,就只能将数据页从磁盘读入到内存,返回给执行;
执行器修改返回的数据
执行器拿到引擎给的行数据,根据update 的条件更新值,得到新的一行数据,再调用引擎接口写入这行新数据;
更新数据
在更新数据的时候会进行一系列的判断,需要对不同索引类型的数据进行不同的操作
如果数据页在内存中
更新的索引为普通索引 :直接更新内存中的数据页更新的索引为唯一性索引 : 会先判断更新后是否会破坏数据的唯一性,不会的话就更新内存中的数据页。
如果数据页不在内存中
更新的索引为普通索引:将对数据页的更新操作记录到change buffer,change buffer会在空闲时异步更新到磁盘。更新的索引为唯一性索引: 因为需要保证更新后的唯一性,所以不能延迟更新,必须把数据页从磁盘加载到内存,然后判断更新后是否会数据冲突,不会的话就更新数据页。
change buffer是什么
redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的是随机读磁盘的IO消耗。
change buffer 对更新过程有加速作用。如果数据页没有在内存中,会将更新操作缓存到 change buffer 中,这样就不需要从磁盘读入这个数据页,减少了 IO 操作,提高了性能。
适合写多读少的场景,因为这样即便立即写了,也不太可能会被访问到,延迟更新可以减少磁盘I/O,只有普通索引会用到,因为唯一性索引,在更新时就需要判断唯一性,所以没有必要。
写redo log (prepare状态)
引擎将这行数据更新到内存中,同时将这个更新操作记录到redo log 里面,将redo log设置为prepare状态
写bin log(commit状态),提交事务
通知MySQL server已经更新操作写入到redo log 了,随时可以提交,将执行的SQL写入到bin log日志,将redo log改成commit状态,事务提交成功。(一个事务是否执行成功的判断依据是是否在bin log中写入成功。写入成功后,即便MySQL Server崩溃,之后恢复时也会根据bin log, redo log进行恢复。)
事务的两阶段提
commit的prepare阶段:引擎把刚刚写入的redo log刷盘commit的commit阶段:引擎binlog刷盘
二段提交制是什么?
更新时,先改内存中的数据页,将更新操作写入redo log日志,此时redo log进入prepare状态,然后通知MySQL Server执行完了,随时可以提交, Server将更新的SQL写入bin log,然MySQL后调用innodb接口将redo log设置为提交状态,更新完成。
如果只是写了bin log就提交,那么忽然发生故障,主节点可以根据redo log恢复数据到最新,但是主从同步时会丢掉这部分更新的数据。
如果只是写binlog,然后写redo log,如果忽然发生故障,主节点根据redo log恢复数据时就会丢掉这部分数据。
MySQL崩溃后,事务恢复时的判断规则是怎么样的?(以redolog是否commit或者binlog是否完整来确定)
如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
a. 如果是,则提交事务;b. 否则,回滚事务。