介绍
本篇博文是自己学习儒猿技术窝)中:《从零开始带你成为MySQL实战优化高手》专栏课程,进行的总结,因为老师写的很好,所以很大一部分直接拿过来使用。
如果想学习完整的知识,请支持正版,地址:从零开始带你称为MySQL实战优化高手(儒猿技术窝)
本文主要讲解了一次数据更新在mysql中经历了哪些过程。
先直接看下我们会讲到哪些步骤,文章中 主要有以下9个步骤:
- (1)更新语句经过 解析,优化 生成执行计划,交由执行器调用存储引擎接口(注:执行器会多次调用存储引擎接口,并不是一次完成)
- (2)查询旧值,先去内存缓冲区查看是否有数据,如果没有,从磁盘中加载到内存,并将旧值写入到 Undo log 日志中,用于回滚数据
- (3)更新内存中的数据(注:磁盘仍为旧数据)
- (4)将操作写入到Redo Log 中
- (5)Redo Log根据刷盘策略刷到磁盘
- (6)准备提交事务,写入binlog 日志(注:binlog也有自己的刷盘策略)
- (7)把本次更新对应的binlog文件名称和这次 更新的binlog日志在文件里的位置,都写入到redo log日志文件里去,同时在redo log日志文件里写入一个commit标 记。
- (8)事务完成
- (9)mysql会有个后台线程将内存数据刷入到磁盘
整体的流程图如下:
更新语句
首先假设我们有一条SQL语句是这样的:
update users set name='xxx' where id=10
那么我们先想一下这条SQL语句是如何执行的?
首先肯定是我们的系统通过一个数据库连接发送到了MySQL上,然后肯定会经过SQL接口、解析器、优化器、执行器 几个环节,解析SQL生成解析树,优化,选取根据Mysql认为最优的执行计划(注:Mysql有着自己的评分算法,在精准度和性能上进行了平衡,因此选取的有可能不是真正的最优的执行计划,但是问题不大,一般来说,都是比较不错的,不要轻易尝试让SQL按自己的想法执行),接着去由执行器负责这个计划的执行,调用InnoDB存储引擎的接口去执 行。
InnoDB的重要内存结构:缓冲池 InnoDB存储引擎中有一个非常重要的放在内存里的组件,就是缓冲池(Buffer Pool),这里面会缓存很多的数据, 以便于以后在查询的时候,万一你要是内存缓冲池里有数据,就可以不用去查磁盘了
这涉及到一个非常重要的优化参数buffer pool 详情可以参考:Mysql 优化之 buffer pool 设置
引擎要执行更新语句的时候 ,比如对“id=10”这一行数据,他其实会先将“id=10”这一行数据看看是否在缓冲池里,如果不在的 话,那么会直接从磁盘里加载到缓冲池里来,而且接着还会对这行记录加独占锁。 因为我们想一下,在我们更新“id=10”这一行数据的时候,肯定是不允许别人同时更新的,所以必须要对这行记录加 独占锁
undo log写入
undo日志文件,记录旧值,用于回滚
假设“id=10”这行数据的name原来是“zhangsan”,现在我们要更新为“xxx”,那么此时我们得先 把要更新的原来的值“zhangsan”和“id=10”这些信息,写入到undo日志文件中去。
更新buffer pool中的缓存数据
当我们把要更新的那行记录从磁盘文件加载到缓冲池,同时对他加锁之后,而且还把更新前的旧值写入undo日志文件 之后,我们就可以正式开始更新这行记录了,更新的时候,先是会更新缓冲池中的记录,此时这个数据就是脏数据 了。 这里所谓的更新内存缓冲池里的数据,意思就是把内存里的“id=10”这行数据的name字段修改为“xxx” 那么为什么说此时这行数据就是脏数据了呢?因为此时内存中的数据并没有刷入磁盘,磁盘仍然是旧值。
思考🤔:万一此时系统宕机,数据会丢失?如何避免?
redo log 写入
答案是肯定不会丢失啦,Mysql 内部使用了 Redo Log Buffer 机制,这也是内存里的一个缓冲区,是用来存 放redo日志的 所谓的redo日志,就是记录下来你对数据做了什么修改,比如对“id=10这行记录修改了name字段的值为xxx”,这 就是一个日志。
此时我们从图上可以看到,Redo Log Buffer 仍然是在 内存缓存中,这有什么意义呢?宕机了,Buffer Pool 和 Redo Log Buffer 一起丢失,直接 一尸两命,这可咋办呢?
继续,我们先思考以下,如果此时宕机会有影响?
其实在数据库中,哪怕执行一条SQL语句,其实也可以是一个独立的事务,只有当你提交事务之 后,SQL语句才算执行结束。
但到目前为止,其实还没有提交事务,那么此时如果MySQL崩溃,必然导致内存里Buffer Pool中 的修改过的数据都丢失,同时你写入Redo Log Buffer中的redo日志也会丢失但现在事务并没有提交,所以就算宕机,内存数据丢失,其实也没有影响,因为磁盘中仍然是原先的旧值,应用端也会直接提示错误。
所以此时如果mysql宕机,不会有任何的问题。
提交事务的时候将redo日志写入磁盘中,注:此时就会根据一定的策略把redo日志从redo log buffer里刷入到磁盘文件里去。
mysql 配置参数:innodb_flush_log_at_trx_commit
- 值为0:那么你提交事务的时候,不会把redo log buffer里的数据刷入磁盘文件的,此时可能你都 提交事务了,结果mysql宕机了,然后此时内存里的数据全部丢失。
- 值为1:你提交事务的时候,就必须把redo log从内存刷入到磁盘文件里去,只要事务提交成功,那么redo log就 必然在磁盘里了。
- 值为2:提交事务的时候,把redo日志写入磁盘文件对应的os cache缓存里去,而不是直接进入磁盘文件,可 能1秒后才会把os cache里的数据写入到磁盘文件里去,此时mysql宕机数据不会丢失,但是如果整个物理机器宕机,那么数据仍然会丢失。
思考下,每个值的优缺点,就继续往下
这里推荐刷盘策略使用值1,,提交事务的时候,redo日志必须是刷入磁盘文件里的(注:这样会消耗一定的资源,响应时间会延长,吞吐量降低)。显示效果如下:
现在来思考🤔:事务已经提交了,redo log 进入了磁盘,但是数据 仍然在内存中,还未刷入磁盘,此时机器宕机,数据会丢失?
答案是不会,因为虽然内存里的修改成name=xxx的数据会丢失,但是redo日志里已经说了,对某某数据做了修改 name=xxx。 所以此时mysql重启之后,mysql会进行检查恢复,会根据redo日志去恢复之前做过的修改,我们看下图。
binlog 写入
如果mysql开始了 二进制文件 binlog ,提交事务的时候,同时会写入binlog
执行器是非常核心的一个组件,负责跟存储引擎配合完成一个SQL语句在磁盘与内存层面的全部数据更新操 作。 而且我们在上图可以看到,我把一次更新语句的执行,拆分为了两个阶段,上图中的1、2、3、4几个步骤,其实本质 是你执行这个更新语句的时候干的事。
然后上图中的5和6两个步骤,是从你提交事务开始的,属于提交事务的阶段了。
对于binlog日志,其实也有不同的刷盘策略,有一个sync_binlog参数可以控制binlog的刷盘策略
- 默认值是0, 此时你把binlog写入磁盘的时候,其实不是直接进入磁盘文件,而是进入os cache内存缓存。此时mysql挂掉不会影响日志刷盘,如果机器宕机,那么就算丢失此次日志。
- 值为1:那么此时会强制在提交事务的时候,把binlog直接写入到磁盘文件里去, 那么这样提交事务之后,哪怕机器宕机,磁盘上的binlog是不会丢失的。
基于binlog和redo log完成事务的提交
当我们把binlog写入磁盘文件之后,接着就会完成最终的事务提交,此时会把本次更新对应的binlog文件名称和这次 更新的binlog日志在文件里的位置,都写入到redo log日志文件里去,同时在redo log日志文件里写入一个commit标 记。 在完成这个事情之后,才算最终完成了事务的提交,意思就是,当走完 1~7全部流程,一次事务才算完成。我们看下图的示意。
🤔思考:最后一步在redo日志中写入commit标记的意义是什么?
他其实是用来保持redo log日志与binlog日志一致的。
如果要是完成步骤6的时候,也就是binlog写入磁盘了,此时mysql宕机了,怎么办?
同理,因为没有redo log中的最终commit标记,因此此时事务提交也是失败的。 必须是在redo log中写入最终的事务commit标记了,然后此时事务提交成功,而且redo log里有本次更新对应的日 志,binlog里也有本次更新对应的日志 ,redo log和binlog完全是一致的。
内存数据刷盘
现在我们知道一次事务已经完成了,但是我们不要忘了,新数据仍然只在内存缓冲池中,尚未刷入磁盘,那么数据是什么时候,如何刷到磁盘的?
所以MySQL有一个后台的IO线程,会在之后某个时间里,随机的把内存buffer pool中的修改后的脏数据给刷回到磁 盘上的数据文件里去
当上图中的IO线程把buffer pool里的修改后的脏数据刷回磁盘的之后,磁盘上的数据才会跟内存里一样,都是 name=xxx这个修改以后的值了!
在你IO线程把脏数据刷回磁盘之前,哪怕mysql宕机崩溃也没关系,因为重启之后,会根据redo日志恢复之前提交事 务做过的修改到内存里去,就是id=10的数据的name修改为了xxx,然后等适当时机,IO线程自然还是会把这个修改 后的数据刷到磁盘上的数据文件里去的
总结
(1)更新语句经过 解析,优化 生成执行计划,交由执行器调用存储引擎接口(注:执行器会多次调用存储引擎接口,并不是一次完成)
(2)查询旧值,先去内存缓冲区查看是否有数据,如果没有,从磁盘中加载到内存,并将旧值写入到 Undo log 日志中,用于回滚数据
(3)更新内存中的数据(注:磁盘仍为旧数据)
(4)将操作写入到Redo Log 中
(5)Redo Log根据刷盘策略刷到磁盘
(6)准备提交事务,写入binlog 日志(注:binlog也有自己的刷盘策略)
(7)把本次更新对应的binlog文件名称和这次 更新的binlog日志在文件里的位置,都写入到redo log日志文件里去,同时在redo log日志文件里写入一个commit标 记。
(8)事务完成
(9)mysql会有个后台线程将内存数据刷入到磁盘
来源:oschina
链接:https://my.oschina.net/u/4393870/blog/4497530