什么是事务的持久性?
当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这就称为持久性。
MySQL是如何实现事务的持久性?
我知道你很急,所以先把结论亮出来
具体的手段就是通过redo日志来实现。
下边的文字想看就看,不看也没关系啦~
每条redo日志的结构差不多记载了发生变更的数据在哪儿(哪个表空间、哪个页号,哪个字节处开始),做了哪些变更。由于日志直接就指向了发生变更的物理地址,所以redo log其实也是个物理日志。
我知道你很急,但是你先别急,不如先看看现在是个什么情况?
请允许我图文介绍下现在的情况
可以看到途中有三个箭头连线,我们看上面两个。
当用户读写数据时,直接访问到的数据其实是内存中的数据,更准确得说,是一块叫做Buffer Pool的内存,而Buffer Pool的数据又来自磁盘。
图中可以看到数据页,其实可以叫页。所谓页,就是一个大小为16kb的内存空间。
我们的内存空间有限,总不能把所有磁盘数据加载到内存里吧,这么整地球都得爆炸!那只加载部分数据,也不能没个章法,所以定义一个“页”的概念,磁盘中的数据就可以被切分成好多页,每页16kb,然后再以页为最小单位,加载若干页到内存,这样数据库的各种管理是不是就很方便了,几乎很多操作就是以页为单位在进行的。
那么问题来了……
既然数据的源头是磁盘,为什么不直接访问磁盘?因为磁盘I/O实在不给力——慢死你,所以MySQL的设计中专门向操作系统申请了一块内存,并起名为Buffer Pool。
在这样的前提之下,用户直接操作的必然内存中的数据,这样肯定不算完:因为我们还没把这些数据刷入磁盘,此时伸出邪恶的小手,轻轻给你把电源插头,你不得吱哇乱叫:数据全特喵没了!
日常反思,可以跳过哈!
所以啊有时候解决一个单一的问题是很简单的,但是当多个问题揉在一起,一个问题的解决方案可能会为另一个问题的解决设置障碍,所以多个问题各自解决方案的组合也很重要,因为不恰当的解决优先级和针对前置问题的不当解决方案,可能会让后续问题的解决陷入僵局。拆分问题,打开思路,回过头来再思考,不要走进死胡同,自勉自省!
那……试试立刻刷盘?
按照我朴素的直觉,我肯定要支招了:那还等什么,赶紧刷盘!嗨~这想法蠢蠢的,怪可爱的啊……为啥?因为I/O很慢,要是能赶紧得起来,也就不会有Buffer Pool的设计啦!
谁不夸她一句救世主:redo log
让我这破锣一样的脑袋想,我是想到下辈子也想不粗来,还好MySQL的大佬们各个身怀绝技,这点问题不在话下,大佬们引入了redo log,专治各种不服。
redo log见文知意
至于redo log这个词儿 ,可难不倒我这个英语六级、张嘴就哑火的chglish杰出用户嘛!这不就是重做日志?“望文生义”一下:就是系统崩溃后,我可以根据重做日志(redo log),把崩溃期间没刷盘的数据变更再做一遍,不就……超完美的好嘛!当然,redo log的功能确实也是如此,并不是我胡说八道哈——看来一个好的名字是可以让知识更加顺利地进入脑袋瓜里的!
客户端发起写操作后,redo log 是如何参与进去的?
上面的图罗列了当客户端发起写操作后,数据库的4个举动:
这个时候就产生了脏页了,脏页也就是用户操作后发生变更的数据页。
就是增加重做日志啦,这个是我们讨论持久性的重点关注对象。至于block,咱们只要知道block的含义其实和“页”是一样的就行,其他不用深究。
redo log 刷盘必须一马当先
注意哟,这几个动作先后顺序很重要,尤其是step3和4,只有保证3在4之前执行,通过redo log做崩溃恢复时,数据才是一致和准确的。此话怎讲?我们来穷举下崩溃的场景:
对了,上述4步不是在一个事务时间内完成的
- 比如“3.将redo log刷入磁盘”,触发redo log刷盘的情况有好几种,比如提交时刷盘,后台线程定时刷盘,另外,是否在提交事务时刷盘,由细分出一个数据库变量innodb_flush_log_at_trx_commit进行控制,不过我们只要记住默认情况下,提交事务时就会将redo log刷入磁盘就行了,其他不用太关心,知道有这么个事儿就行了;具体内容可以参掘金小册
- 此外“4.将脏页刷回磁盘”这一神圣的任务是单独交给一个后台线程做的,会定时地、根据一系列的规则将脏页刷盘;所以从上面两个栗子来看,这4步不一定是在一个事务期间发生的,不过,我们把时间拉长看,这四个步骤一定是按照这样的先后顺序发生的。
具体内容可以参掘金小册中的“刷新脏页到磁盘”一节
最后的碎碎念
其实说到这里,关于MySQL是如何通过redo log实现事务四大特性的持久性的,也已经写得差不多了,欢迎大家指正和建议!
本文知识点主要参考稀土掘金网站的《MySQL 是怎样运行的:从根儿上理解 MySQL》小册的第19、21、22章,好记性它不如烂笔头呀!