加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 黄冈站长网 (http://www.0713zz.com/)- 数据应用、建站、人体识别、智能机器人、语音技术!
当前位置: 首页 > 站长学院 > MySql教程 > 正文

MySQL的crash-safe原理是啥

发布时间:2022-01-21 03:05:37 所属栏目:MySql教程 来源:互联网
导读:这篇文章主要讲解了MySQL的crash-safe原理是什么,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习MySQL的crash-safe原理是什么吧! MySQL作为当下最流行的开源关系型数据库,有一个很关键和基本的能力,就
       这篇文章主要讲解了“MySQL的crash-safe原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MySQL的crash-safe原理是什么”吧!
  
      MySQL作为当下最流行的开源关系型数据库,有一个很关键和基本的能力,就是必须能够保证数据不会丢。那么在这个能力背后,MySQL是如何设计才能保证不管在什么时间崩溃,恢复后都能保证数据不会丢呢?有哪些关键技术支撑了这个能力?本文将为我们一一揭晓。
 
一、前言
MySQL 保证数据不会丢的能力主要体现在两方面:
 
能够恢复到任何时间点的状态;
能够保证MySQL在任何时间段突然奔溃,重启后之前提交的记录都不会丢失;
对于第一点将MySQL恢复到任何时间点的状态,相信很多人都知道,只要保留有足够的binlog,就能通过重跑binlog来实现。
  
因为crash-safe主要体现在事务执行过程中突然奔溃,重启后能保证事务完整性,所以在讲解具体原理之前,先了解下MySQL事务执行有哪些关键阶段,后面才能依据这几个阶段来进行解析。下面以一条更新语句的执行流程为例,话不多说,直接上图:
 
从上图可以清晰地看出一条更新语句在MySQL中是怎么执行的,简单进行总结一下:
 
从内存中找出这条数据记录,对其进行更新;
将对数据页的更改记录到redo log中;
将逻辑操作记录到binlog中;
对于内存中的数据和日志,都是由后台线程,当触发到落盘规则后再异步进行刷盘;
上面演示了一条更新语句的详细执行过程,接下来咱们通过解答问题,带着问题来剖析这个crash-safe的设计原理。
 
二、WAL机制
问题:为什么不直接更改磁盘中的数据,而要在内存中更改,然后还需要写日志,最后再落盘这么复杂?
 
这个问题相信很多同学都能猜出来,MySQL更改数据的时候,之所以不直接写磁盘文件中的数据,最主要就是性能问题。因为直接写磁盘文件是随机写,开销大性能低,没办法满足MySQL的性能要求。所以才会设计成先在内存中对数据进行更改,再异步落盘。但是内存总是不可靠,万一断电重启,还没来得及落盘的内存数据就会丢失,所以还需要加上写日志这个步骤,万一断电重启,还能通过日志中的记录进行恢复。
 
写日志虽然也是写磁盘,但是它是顺序写,相比随机写开销更小,能提升语句执行的性能(针对顺序写为什么比随机写更快,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多)。
 
这个技术就是大多数存储系统基本都会用的WAL(Write Ahead Log)技术,也称为日志先行的技术,指的是对数据文件进行修改前,必须将修改先记录日志。保证了数据一致性和持久性,并且提升语句执行性能。
 
三、核心日志模块
问题:更新SQL语句执行流程中,总共需要写3个日志,这3个是不是都需要,能不能进行简化?
 
更新SQL执行过程中,总共涉及MySQL日志模块其中的三个核心日志,分别是redo log(重做日志)、undo log(回滚日志)、binlog(归档日志)。这里提前预告,crash-safe的能力主要依赖的就是这三大日志。
 
接下来,针对每个日志将单独介绍各自的作用,然后再来评估是否能简化掉。
 
1、重做日志 redo log
 
redo log也称为事务日志,由InnoDB存储引擎层产生。记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置,因为修改会覆盖之前的)。
 
前面提到的WAL技术,redo log就是WAL的典型应用,MySQL在有事务提交对数据进行更改时,只会在内存中修改对应的数据页和记录redo log日志,完成后即表示事务提交成功,至于磁盘数据文件的更新则由后台线程异步处理。由于redo log的加入,保证了MySQL数据一致性和持久性(即使数据刷盘之前MySQL奔溃了,重启后仍然能通过redo log里的更改记录进行重放,重新刷盘),此外还能提升语句的执行性能(写redo log是顺序写,相比于更新数据文件的随机写,日志的写入开销更小,能显著提升语句的执行性能,提高并发量),由此可见redo log是必不可少的。
 
redo log是固定大小的,所以只能循环写,从头开始写,写到末尾就又回到开头,相当于一个环形。当日志写满了,就需要对旧的记录进行擦除,但在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。在redo log满了到擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求,所以有可能会导致MySQL卡顿。(所以针对并发量大的系统,适当设置redo log的文件大小非常重要!!!)
 
2、回滚日志 undo log
 
undo log顾名思义,主要就是提供了回滚的作用,但其还有另一个主要作用,就是多个行版本控制(MVCC),保证事务的原子性。在数据修改的流程中,会记录一条与当前操作相反的逻辑日志到undo log中(可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录),如果因为某些原因导致事务异常失败了,可以借助该undo log进行回滚,保证事务的完整性,所以undo log也必不可少。
 
3、归档日志 binlog
 
binlog在MySQL的server层产生,不属于任何引擎,主要记录用户对数据库操作的SQL语句(除了查询语句)。之所以将binlog称为归档日志,是因为binlog不会像redo log一样擦掉之前的记录循环写,而是一直记录(超过有效期才会被清理),如果超过单日志的最大值(默认1G,可以通过变量 max_binlog_size 设置),则会新起一个文件继续记录。但由于日志可能是基于事务来记录的(如InnoDB表类型),而事务是绝对不可能也不应该跨文件记录的,如果正好binlog日志文件达到了最大值但事务还没有提交则不会切换新的文件记录,而是继续增大日志,所以 max_binlog_size 指定的值和实际的binlog日志大小不一定相等。
 
正是由于binlog有归档的作用,所以binlog主要用作主从同步和数据库基于时间点的还原。
 
那么回到刚才的问题,binlog可以简化掉吗?这里需要分场景来看:
 
如果是主从模式下,binlog是必须的,因为从库的数据同步依赖的就是binlog;
如果是单机模式,并且不考虑数据库基于时间点的还原,binlog就不是必须,因为有redo log就可以保证crash-safe能力了;但如果万一需要回滚到某个时间点的状态,这时候就无能为力,所以建议binlog还是一直开启;
根据上面对三个日志的详解,我们可以对这个问题进行解答:在主从模式下,三个日志都是必须的;在单机模式下,binlog可以视情况而定,保险起见最好开启。
 
四、两阶段提交
问题:为什么redo log要分两步写,中间再穿插写binlog呢?
 
从上面可以看出,因为redo log影响主库的数据,binlog影响从库的数据,所以redo log和binlog必须保持一致才能保证主从数据一致,这是前提。
  
从图中可看出,事务的提交过程有两个阶段,就是将redo log的写入拆成了两个步骤:prepare和commit,中间再穿插写入binlog。
 
如果这时候你很疑惑,为什么一定要用两阶段提交呢,如果不用两阶段提交会出现什么情况,比如先写redo log,再写binlog或者先写binlog,再写redo log不行吗?下面我们用反证法来进行论证。
 
我们继续用update T set c=c+1 where id=2这个例子,假设id=2这一条数据的c初始值为0。那么在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于redo log已经写完了,系统重启后会通过redo log将数据恢复回来,所以恢复后这一行c的值是1。但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,不管是现在的从库还是之后通过这份binlog还原临时库都没有这一次更新,c的值还是0,与原库的值不同。
 
同理,如果先写binlog,再写redo log,中途系统crash了,也会导致主从不一致,这里就不再详述。
 
所以将redo log分成两步写,即两阶段提交,才能保证redo log和binlog内容一致,从而保证主从数据一致。
 
两阶段提交虽然能够保证单事务两个日志的内容一致,但在多事务的情况下,却不能保证两者的提交顺序一致,比如下面这个例子,假设现在有3个事务同时提交:
 
T1 (--prepare--binlog---------------------commit)
T2 (-----prepare-----binlog----commit)
T3 (--------prepare-------binlog------commit)
解析:
 
    redo log prepare的顺序:T1 --》T2 --》T3
 
    binlog的写入顺序:T1 --》 T2 --》T3
 
    redo log commit的顺序:T2 --》 T3 --》T1
 
结论:由于binlog写入的顺序和redo log提交结束的顺序不一致,导致binlog和redo log所记录的事务提交结束的顺序不一样,最终导致的结果就是主从数据不一致。
 
因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。所以在早期的MySQL版本中,通过使用prepare_commit_mutex锁来保证事务提交的顺序,在一个事务获取到锁时才能进入prepare,一直到commit结束才能释放锁,下个事务才可以继续进行prepare操作。通过加锁虽然完美地解决了顺序一致性的问题,但在并发量较大的时候,就会导致对锁的争用,性能不佳。除了锁的争用会影响到性能之外,还有一个对性能影响更大的点,就是每个事务提交都会进行两次fsync(写磁盘),一次是redo log落盘,另一次是binlog落盘。大家都知道,写磁盘是昂贵的操作,对于普通磁盘,每秒的QPS大概也就是几百。
 
五、组提交
问题:针对通过在两阶段提交中加锁控制事务提交顺序这种实现方式遇到的性能瓶颈问题,有没有更好的解决方案呢?
 
答案自然是有的,在MySQL 5.6 就引入了binlog组提交,即BLGC(Binary Log Group Commit)。binlog组提交的基本思想是,引入队列机制保证InnoDB commit顺序与binlog落盘顺序一致,并将事务分组,组内的binlog刷盘动作交给一个事务进行,实现组提交目的。 
 
 

(编辑:PHP编程网 - 黄冈站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读