比较喜欢的一段话:不经一番寒彻骨,怎得梅花扑鼻香,学习是枯燥的请大家坚持!
对于一个SQL语句的更新来说,前面的流程都可以说类似的,通过解析器进行语法分析,优化器优化,执行引擎去执行,这个都没有什么问题,重点在于多了一点东西,那就是redo_log、undo_log和binlog。
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!
所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【111】即可免费获取
在说日志模块前,先说一下什么是物理日志和逻辑日志。
物理日志:通俗的讲,就是只有"我"自己可以使用,别人无法共享我的"物理格式,私有化。
逻辑日志: 可以给别的引擎使用,是所有引擎共享的。
「re」在英文中的词根含义是“重新”,redo就是「重新做」的意思,顾名思义就是MySQL根据这个日志文件重新进行操作
redo log是 InnoDB 引擎特有的日志,又被称为重写日志, 用来记录事务操作的变化,记录的是数据修改之后的值,不管事务提交是否成功,都会被记录下来。它让MySQL拥有了崩溃恢复能力。
比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
以常见的古代酒馆掌柜记账举例:
酒馆掌柜有一个黑板,赊账的人少时就记在黑板上如果赊账人多的话,由于黑板的空间大小有限,所以他又需要额外准备一本账本,专门记录所有赊账的账目。 如果有人要赊账的话,一般老板有两种做法:
如果掌柜使用第一种方法的话,每当有人要赊账的话,首先他需要打开厚厚的账本,一页一页查找该顾客的姓名,然后进行登记。你想一下,如果赊账的人不多,掌柜找赊账人的记录轻松点,如果赊账本有好几本的话,一本一本的找,掌柜看的都头疼。就存在一个查找顾客的姓名效率的问题。
在 MySQL里也有这个问题。如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。
而黑板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL (Write Ahead Logging)技术,它的关键点就是先写日志,再写磁盘,
当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(黑板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。
如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把黑板中的一部分赊账记录更新到账本中,然后把这些记录从黑板上擦掉,为记新账腾出空间。
与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“黑板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下图所示:
write pos是当前记录的位置,一边写一边后移,写到4号文件末尾就回到1号文件开头。check point是当前要把记录写入到数据文件的位置,也是后移并且循环的。
如果和上面老板黑板场景结合起来描述的话,write pos就是老板在黑板上顺序写入赊账人记录位置,对于mysql来说,write pos后移;而check point就是老板把黑板上记录写入到赊账本上的位置,当老板写入到赊账本上后,就会把粉板上该记录擦除掉,对于mysql来说,check point后移。
write pos 和 checkpoint 之间的是“黑板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 check point,表示“黑板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 check point 推进一下。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
在MySQL中,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、随机查找成本(因为操作的数据散落在磁盘各处)都很高。为了解决这个问题,MySQL使用了WAL技术(Write Ahead Logging),也称为日志先行技术,它的关键点就是先写日志,再写磁盘。写日志虽然也是写磁盘,但是它是顺序写,相比随机写开销更小,能提升语句执行的性能。WAL技术保证了数据一致性和持久性,并且提升语句执行性能。
缓冲池内存中的数据已经更新好了,那么接下来就该开始写redo_log了,只是redo_log也不是直接写文件的,一般都是这样对吧,直接写的话性能太差了,所以就有redo_log_buffer叫做redo_log缓冲。
在写redo_log的时候先把数据写到redo_log缓冲区,然后异步写入磁盘,很显然,极端情况下会有丢失数据的可能。控制这个刷盘策略的的参数叫做
innodb_flush_log_at_trx_commit。
innodb_flush_log_at_trx_commit参数说明
参数 |
描述 |
0 |
提交事务时不会写入磁盘。 |
1 |
提交事务一定会进行一次刷盘,同步当然性能最差,但是也最安全。(默认且推荐策略) |
2 |
提交事务的时候,先把日志刷入磁盘文件对应的 os cache 缓存里,隔一段时间再把数据刷入磁盘。 |
redo log的存在使得数据库具有crash-safe能力,即如果Mysql 进程异常重启了,系统会自动去检查redo log,将未写入到Mysql的数据从redo log恢复到Mysql中去。
数据库的crash-safe保证的是:
MySQL 整体来看,其实就有两块:一块是 Server层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
binlog是mysql数据库service层的,是所有存储引擎共享的日志模块,它用于记录数据库执行的写入性操作,也就是在事务commit阶段进行记录,以二进制的形式保存于磁盘中。
这两种日志有以下不同:
redo log |
binlog |
InnoDB 引擎特有的。 |
binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 |
redo log 是物理日志,记录的是“在某个数据页上做了什么修改。” |
binlog 是逻辑日志,并且由mysql数据库的service层执行。记录的是这个语句的原始逻辑,比如 “给 ID=4 这一行的 score 字段加 1 ”。 |
redo log 是循环写的,空间固定会用完。 |
binlog 是可以追加写入的。可以通过 max_binlog_size 参数设置bin log文件大小,当文件大小达到某个值时,会生成新的文件来保存日志。 |
update 语句的执行流程图如下
重点看下最后三步,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。
两阶段提交不是MySQL的专利,两阶段提交是一种跨系统维持数据逻辑一致性的常见方案,尤其在分布式事务上,所以请读者重点体会思想
我们把redo日志的提交分成两步,两步中redo日志的状态分别是prepare和commit。步骤如下
为什么必须有“两阶段提交”呢?由于 redo log 和 binlog 是两个独立的逻辑,两阶段提交这是为了让两份日志之间的逻辑一致。
如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
在写完redo log之后,数据此时具有crash-safe能力,因此系统崩溃,数据会恢复成事务开始之前的状态。但是,若在redo log写完时候,binlog写入之前,系统发生了宕机。此时binlog没有对上面的更新语句进行保存,导致当使用binlog进行数据库的备份或者恢复时,就少了上述的更新语句。从而使得id=2这一行的数据没有被更新。
写完binlog之后,所有的语句都被保存,所以通过binlog复制或恢复出来的数据库中id=2这一行的数据会被更新为a=1。但是如果在redo log写入之前,系统崩溃,那么redo log中记录的这个事务会无效,导致实际数据库中id=2这一行的数据并没有更新。
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
对于InnoDB引擎而言,在每次事务commit提交时才会记录binlog日志,此时记录仍然在内存中,那么什么时候存储到磁盘中呢?mysql通过 sync_binlog 参数控制binlog刷盘时机,取值范围:0~N:
注:该值默认为0,采用操作系统机制进行缓冲数据同步。 sync_binlog 参数建议设置为1,这样每次事务commit时就会把bin log写入磁盘中,这样也可以保证mysql异常重启之后bin log日志不会丢失。
binlog是实现MySQL主从复制功能的核心组件。
master节点会将所有的写操作记录到binlog中,slave节点会有专门的I/O线程读取master节点的binlog,将写操作同步到当前所在的slave节点。
通过 binlog 获取想要恢复的时间段数据
undo log(撤销日志或回滚日志)记录了事务发生之前的数据状态,分为insert undo log和update undo log。
如果修改数据时出现异常,可以用 undo log来实现回滚操作(保持原子性)。可以理解为undo日志记录的是反向的操作,比如INSERT操作会记录DELETE,UPDATE会记录UPDATE之前的值,和redo日志记录在哪个物理页面做了什么操作不同,所以这是一种逻辑格式的日志。
undo日志和redo日志与事务密切相关,被统称为「事务日志」。