MySQL是如何保证数据不丢的

技术MySQL是如何保证数据不丢的本篇内容主要讲解“MySQL是如何保证数据不丢的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“MySQL是如何保证数据不丢的”吧!

本文内容主要讲解“MySQL如何保证数据不丢失”。感兴趣的朋友不妨看看。本文介绍的方法简单、快速、实用。下面让边肖带大家学习“MySQL如何保证数据不丢失”!

MySQL是如何保证数据不丢的

图1两阶段提交示意图

这个图不是更新语句的执行过程吗?为什么可以称之为commit语句?原因是为了迷惑两个“commit”的概念:

通常的提交语句指的是MySQL语法中用于提交事务的命令。通常与开始/开始事务一起使用。

我们的图中使用的提交步骤是指事务提交过程中的一个小步骤和最后一步。完成此步骤后,提交交易。

当提交语句被执行时,它将包括提交步骤。

在我们的示例中,事务没有被显式启动,所以这个update语句自己就是一个事务将在执行后提交事务时使用这个“提交步骤”。

接下来,我们将一起分析在两阶段提交的不同时刻,MySQL异常重启会出现什么现象。

如果图中时间A出现崩溃,即写重做日志后处于准备阶段,写binlog前,由于此时binlog还没有写,重做日志还没有提交,崩溃恢复时会回滚此事务。此时binlog还没有写,所以不会传输到备份库。在这里,大家都能理解。

如果binlog完成后发生崩溃,重做日志没有提交,当崩溃恢复后MySQL会怎么做?

先来看看崩溃恢复的判断规则。

如果重做日志中的事务完成,即已经有提交标记,则直接提交;

如果重做日志中的事务只有完整的准备,则判断对应的事务binlog是否存在且完整:

A.如果是,提交事务;

B.否则,回滚事务。

这里,时间B的崩溃对应于2(a)的情况,事务将在崩溃恢复过程中提交。

现在,让我们继续扩展这个问题。

00-1010回答:事务的binlog有完整的格式:

以语句格式Binlog,最后会有COMMIT;

行格式binlog,最后会有一个XID事件。

此外,在MySQL 5 . 6 . 2版本之后,引入了binlog-checksum参数来验证binlog内容的正确性。MySQL可以检查校验和的结果,发现binlog日志可能会因为磁盘原因在日志中间出错。所以,MySQL还是有办法验证事务binlog的完整性的。

00-1010回答:他们有一个共同的数据字段,叫做XID。崩溃恢复后,将依次扫描重做日志:

如果遇到同时准备和提交的重做日志,直接提交;

如果遇到只有parepare但没有commit的重做日志,请将XID带到binlog以查找相应的事务。

00-1010回答:其实这个问题和我们在反证法中提到的数据和备份的一致性有关。在时间b,即写完binlog后MySQL崩溃。此时,binlog已经被写入,然后它将被从库(或用这个binlog恢复的库)使用。

因此,该事务也应该在主库上提交。采用这种策略,主库和备用库的数据是一致的。

追问1:MySQL怎么知道binlog是完整的?

og写完,再写binlog。崩溃恢复的时候,必须得两个日志都完整才可以。是不是一样的逻辑?

回答:其实,两阶段提交是经典的分布式系统问题,并不是MySQL独有的。

如果必须要举一个场景,来说明这么做的必要性的话,那就是事务的持久性问题。

对于InnoDB引擎来说,如果redo log提交完成了,事务就不能回滚(如果这还允许回滚,就可能覆盖掉别的事务的更新)。而如果redo log直接提交,然后binlog写入的时候失败,InnoDB又回滚不了,数据和binlog日志又不一致了。

两阶段提交就是为了给所有人一个机会,当每个人都说“我ok”的时候,再一起提交。

追问5:不引入两个日志,也就没有两阶段提交的必要了。只用binlog来支持崩溃恢复,又能支持归档,不就可以了?

回答:这位同学的意思是,只保留binlog,然后可以把提交流程改成这样:… -> “数据更新到内存” -> “写 binlog” -> “提交事务”,是不是也可以提供崩溃恢复的能力?

答案是不可以。

如果说历史原因的话,那就是InnoDB并不是MySQL的原生存储引擎。MySQL的原生引擎是MyISAM,设计之初就有没有支持崩溃恢复。

InnoDB在作为MySQL的插件加入MySQL引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。

InnoDB接入了MySQL后,发现既然binlog没有崩溃恢复的能力,那就用InnoDB原有的redo log好了。

而如果说实现上的原因的话,就有很多了。就按照问题中说的,只用binlog来实现崩溃恢复的流程,我画了一张示意图,这里就没有redo log了。

MySQL是如何保证数据不丢的

                                                                   图2 只用binlog支持崩溃恢复

这样的流程下,binlog还是不能支持崩溃恢复的。我说一个不支持的点吧:binlog没有能力恢复“数据页”。

如果在图中标的位置,也就是binlog2写完了,但是整个事务还没有commit的时候,MySQL发生了crash。

重启后,引擎内部事务2会回滚,然后应用binlog2可以补回来;但是对于事务1来说,系统已经认为提交完成了,不会再应用一次binlog1。

但是,InnoDB引擎使用的是WAL技术,执行事务的时候,写完内存和日志,事务就算完成了。如果之后崩溃,要依赖于日志来恢复数据页。

也就是说在图中这个位置发生崩溃的话,事务1也是可能丢失了的,而且是数据页级的丢失。此时,binlog里面并没有记录数据页的更新细节,是补不回来的。

你如果要说,那我优化一下binlog的内容,让它来记录数据页的更改可以吗?但,这其实就是又做了一个redo log出来。

所以,至少现在的binlog能力,还不能支持崩溃恢复。

追问6:那能不能反过来,只用redo log,不要binlog?

回答:如果只从崩溃恢复的角度来讲是可以的。你可以把binlog关掉,这样就没有两阶段提交了,但系统依然是crash-safe的。

但是,如果你了解一下业界各个公司的使用场景的话,就会发现在正式的生产库上,binlog都是开着的。因为binlog有着redo log无法替代的功能。

一个是归档。redo log是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留,redo log也就起不到归档的作用。

一个就是MySQL系统依赖于binlog。binlog作为MySQL一开始就有的功能,被用在了很多地方。其中,MySQL系统高可用的基础,就是binlog复制。

还有很多公司有异构系统(比如一些数据分析系统),这些系统就靠消费MySQL的binlog来更新自己的数据。关掉binlog的话,这些下游系统就没法输入了。

总之,由于现在包括MySQL高可用在内的很多系统机制都依赖于binlog,所以“鸠占鹊巢”redo log还做不到。你看,发展生态是多么重要。

追问7:redo log一般设置多大?

回答:redo log太小的话,会导致很快就被写满,然后不得不强行刷redo log,这样WAL机制的能力就发挥不出来了。

所以,如果是现在常见的几个TB的磁盘的话,就不要太小气了,直接将redo log设置为4个文件、每个文件1GB吧。

追问8:正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的呢?

回答:这个问题其实问得非常好。这里涉及到了,“redo log里面到底是什么”的问题。

实际上,redo log并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由redo log更新过去”的情况。

  1. 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与redo log毫无关系。

  2. 在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让redo log更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。

追问9:redo log buffer是什么?是先修改内存,还是先写redo log文件?

在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:

begin;
insert into t1 ...
insert into t2 ...
commit;

这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。

所以,redo log buffer就是一块内存,用来先存redo日志的。也就是说,在执行第一个insert的时候,数据的内存被修改了,redo log buffer也写入了日志。

但是,真正把日志写到redo log文件(文件名是 ib_logfile+数字),是在执行commit语句的时候做的。

(这里说的是事务执行过程中不会“主动去刷盘”,以减少不必要的IO消耗。但是可能会出现“被动写入磁盘”,比如内存不够、其他事务提交等情况)。

单独执行一个更新语句的时候,InnoDB会自己启动一个事务,在语句执行完成的时候提交。过程跟上面是一样的,只不过是“压缩”到了一个语句里面完成。

以上这些问题,就是把大家提过的关于redo log和binlog的问题串起来,做的一次集中回答。如果你还有问题,可以在评论区继续留言补充。

WAL 的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。 提升性能的核心机制,也的确是尽量减少随机读写

结论:只要redo log和binlog保证持久化到磁盘,就能确保MySQL异常重启后,数据可以恢复。

但是redo log的写入流程是怎么样的,如何保证redo log真实地写入了磁盘。

binlog的写入机制

其实,binlog的写入逻辑比较简单:事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

一个事务的binlog是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了binlog cache的保存问题。

系统给binlog cache分配了一片内存,每个线程一个,参数 binlog_cache_size用于控制单个线程内binlog cache所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

事务提交的时候,执行器把binlog cache里的完整事务写入到binlog中,并清空binlog cache。状态如图1所示。

MySQL是如何保证数据不丢的

                                                                               图1 binlog写盘状态

可以看到,每个线程有自己binlog cache,但是共用同一份binlog文件。

  • 图中的write,指的就是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快。

  • 图中的fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为fsync才占磁盘的IOPS。

write 和fsync的时机,是由参数sync_binlog控制的:

  1. sync_binlog=0的时候,表示每次提交事务都只write,不fsync;

  2. sync_binlog=1的时候,表示每次提交事务都会执行fsync;

  3. sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N个事务后才fsync。

因此,在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成0,比较常见的是将其设置为100~1000中的某个数值。

但是,将sync_binlog设置为N,对应的风险是:如果主机发生异常重启,会丢失最近N个事务的binlog日志。

redo log的写入机制

接下来,我们再说说redo log的写入机制。上文介绍了redo log buffer。事务在执行过程中,生成的redo log是要先写到redo log buffer的。

然后就有同学问了,redo log buffer里面的内容,是不是每次生成后都要直接持久化到磁盘呢?答案是,不需要。

如果事务执行期间MySQL发生异常重启,那这部分日志就丢了。由于事务并没有提交,所以这时日志丢了也不会有损失。

那么,另外一个问题是,事务还没提交的时候,redo log buffer中的部分日志有没有可能被持久化到磁盘呢?

答案是,确实会有。

这个问题,要从redo log可能存在的三种状态说起。这三种状态,对应的就是图2 中的三个颜色块。

MySQL是如何保证数据不丢的

图3 redo log 组提交

从图中可以看到,

  1. trx1是第一个到达的,会被选为这组的 leader;

  2. 等trx1要开始写盘的时候,这个组里面已经有了三个事务,这时候LSN也变成了160;

  3. trx1去写盘的时候,带的就是LSN=160,因此等trx1返回时,所有LSN小于等于160的redo log,都已经被持久化到磁盘;

  4. 这时候trx2和trx3就可以直接返回了。

所以,一次组提交里面,组员越多,节约磁盘IOPS的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。

在并发更新场景下,第一个事务写完redo log buffer以后,接下来这个fsync越晚调用,组员可能越多,节约IOPS的效果就越好。

为了让一次fsync带的组员更多,MySQL有一个很有趣的优化:拖时间。在介绍两阶段提交的时候,我曾经给你画了一个图,现在我把它截过来。

MySQL是如何保证数据不丢的

图5 两阶段提交细化

这么一来,binlog也可以组提交了。在执行图5中第4步把binlog fsync到磁盘时,如果有多个事务的binlog已经写完了,也是一起持久化的,这样也可以减少IOPS的消耗。

不过通常情况下第3步执行得会很快,所以binlog的write和fsync间的间隔时间短,导致能集合到一起持久化的binlog比较少,因此binlog的组提交的效果通常不如redo log的效果那么好。

如果你想提升binlog组提交的效果,可以通过设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count来实现。

  1. binlog_group_commit_sync_delay参数,表示延迟多少微秒后才调用fsync;

  2. binlog_group_commit_sync_no_delay_count参数,表示累积多少次以后才调用fsync。

这两个条件是或的关系,也就是说只要有一个满足条件就会调用fsync。

所以,当binlog_group_commit_sync_delay设置为0的时候,binlog_group_commit_sync_no_delay_count也无效了。

之前有同学在评论区问到,WAL机制是减少磁盘写,可是每次提交事务都要写redo log和binlog,这磁盘读写次数也没变少呀?

现在你就能理解了,WAL机制主要得益于两个方面:

  1. redo log 和 binlog都是顺序写,磁盘的顺序写比随机写速度要快;

  2. 组提交机制,可以大幅度降低磁盘的IOPS消耗。

分析到这里,我们再来回答这个问题:如果你的MySQL现在出现了性能瓶颈,而且瓶颈在IO上,可以通过哪些方法来提升性能呢?

针对这个问题,可以考虑以下三种方法:

  1. 设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count参数,减少binlog的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。

  2. 将sync_binlog 设置为大于1的值(比较常见是100~1000)。这样做的风险是,主机掉电时会丢binlog日志。

  3. 将innodb_flush_log_at_trx_commit设置为2。这样做的风险是,主机掉电的时候会丢数据。

我不建议你把innodb_flush_log_at_trx_commit 设置成0。因为把这个参数设置成0,表示redo log只保存在内存中,这样的话MySQL本身异常重启也会丢数据,风险太大。而redo log写到文件系统的page cache的速度也是很快的,所以将这个参数设置成2跟设置成0其实性能差不多,但这样做MySQL异常重启时就不会丢数据了,相比之下风险会更小。

小结

在专栏的第2篇和第15篇文章中,我和你分析了,如果redo log和binlog是完整的,MySQL是如何保证crash-safe的。今天这篇文章,我着重和你介绍的是MySQL是“怎么保证redo log和binlog是完整的”。

希望这三篇文章串起来的内容,能够让你对crash-safe这个概念有更清晰的理解。

之前的第15篇答疑文章发布之后,有同学继续留言问到了一些跟日志相关的问题,这里为了方便你回顾、学习,我再集中回答一次这些问题。

问题1:执行一个update语句以后,我再去执行hexdump命令直接查看ibd文件内容,为什么没有看到数据有改变呢?

回答:这可能是因为WAL机制的原因。update语句执行完成后,InnoDB只保证写完了redo log、内存,可能还没来得及将数据写到磁盘。

问题2:为什么binlog cache是每个线程自己维护的,而redo log buffer是全局共用的?

回答:MySQL这么设计的主要原因是,binlog是不能“被打断的”。一个事务的binlog必须连续写,因此要整个事务完成后,再一起写到文件里。

而redo log并没有这个要求,中间有生成的日志可以写到redo log buffer中。redo log buffer中的内容还能“搭便车”,其他事务提交的时候可以被一起写到磁盘中。

问题3:事务执行期间,还没到提交阶段,如果发生crash的话,redo log肯定丢了,这会不会导致主备不一致呢?

回答:不会。因为这时候binlog 也还在binlog cache里,没发给备库。crash以后redo log和binlog都没有了,从业务角度看这个事务也没有提交,所以数据是一致的。

问题4:如果binlog写完盘以后发生crash,这时候还没给客户端答复就重启了。等客户端再重连进来,发现事务已经提交成功了,这是不是bug?

回答:不是。

你可以设想一下更极端的情况,整个事务都提交成功了,redo log commit完成了,备库也收到binlog并执行了。但是主库和客户端网络断开了,导致事务成功的包返回不回去,这时候客户端也会收到“网络断开”的异常。这种也只能算是事务成功的,不能认为是bug。

实际上数据库的crash-safe保证的是:

  1. 如果客户端收到事务成功的消息,事务就一定持久化了;

  2. 如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;

  3. 如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。

到此,相信大家对“MySQL是如何保证数据不丢的”有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/137954.html

(0)

相关推荐

  • C语言中如何求两个矩阵的乘积

    技术C语言中如何求两个矩阵的乘积这篇文章将为大家详细讲解有关C语言中如何求两个矩阵的乘积,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。分析在数学中,矩阵(Matrix)是

    攻略 2021年11月11日
  • sql语句优化方法有哪些(sql优化常用面试题)

    技术SQL优化方法有哪些这篇文章主要讲解了“SQL优化方法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SQL优化方法有哪些”吧!一、避免进行null判断 应

    攻略 2021年12月22日
  • debug D命令如何查看指定范围内的内容

    技术debug D命令如何查看指定范围内的内容今天就跟大家聊聊有关debug D命令如何查看指定范围内的内容,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。    

    攻略 2021年11月11日
  • js判断某个元素是否有指定的class名的方法有哪些

    技术js判断某个元素是否有指定的class名的方法有哪些这篇文章主要介绍“js判断某个元素是否有指定的class名的方法有哪些”,在日常操作中,相信很多人在js判断某个元素是否有指定的class名的方法有哪些问题上存在疑

    攻略 2021年11月5日
  • css 背景渐变(如何通过css实现背景颜色渐变?)

    技术css3如何将背景设置为渐变色小编给大家分享一下css3如何将背景设置为渐变色,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

    攻略 2021年12月19日
  • window10上登录Oracle时提示ORA-12546错误怎么办

    技术window10上登录Oracle时提示ORA-12546错误怎么办这篇文章主要介绍window10上登录Oracle时提示ORA-12546错误怎么办,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要

    2021年11月20日