灵魂发问:mysql真的可靠么?

背景

业务开发中我们选择mysql作为数据库存储,认为将数据写入就认为大功告成了,那么现实情况真的有这么乐观么?如果现在写入的一条数据可能是价值1000w的成交订单凭证,你还能百分之百的放心把数据交给他来保管么,我猜这时候你可能会想

  • 万一db挂了数据丢了咋办?
  • 万一返回成功但是实际写入失败咋办?
  • 万一出现万一了,我饭碗还保得住么?

数据库作为数据存储媒介承载了系统运行中的重要数据,其可靠性尤为重要的,而mysql作为主流数据库,有必要了解其数据可靠性是如何实现的,无论对于问题的排查还是方案的设计都是大有裨益的。下面我想从以下几个方面来讨论:

  • 如何保证写不丢
  • 如何保证写不错
  • 如何保证高可用

一、如何保证写不丢

实现写不丢意味着只要客户端收到写入成功的指令之后,我们就可以百分之百相信数据已经存储在db中,并且在写入后任意时刻都可以查询到该数据。

乍一想其实还挺好实现的,不就是只要在写文件成功落盘后再返回给你成功不就行了么,有一种“我上我也行”的错觉,然而事情其实远没有这么简单。如果只是简单的一个语句的执行如数据的插入这确实可以这么来实现,但是如果是事务的场景下呢?要保证多条语句同时写入成功,遇到异常情况下还需要触发回滚,那么这时候问题就变得复杂起来了,比如写到一半失败了咋办,回滚失败咋办,另外我们还需要考虑到写入的效率问题。

事务持久性

这里首当其冲就是考虑事务持久性,因为写不丢的复杂场景主要是在事务执行阶段,也就是我们需要了解事务持久性的具体实现。事务持久性离不开两个重要日志redolog和binlog,这两个文件保障了事务执行期间的数据一致性,在此先简单对两个日志的作用做介绍

日志定义
redolog重做日志用来实现事务持久性,主要有两部分文件组成,重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者是在磁盘中。
binlog归档日志。记录了所有的DDL和DML语句(除查询语句外),以事件形式记录,是事务安全型。

redolog的引入主要是为了解决事务的持久性,也就是既然我们要同时成功同时失败,那么我们就需要有一个临时的记录表来记录这些事务内的信息,等到事务提交后再一起刷到db存储表中;binglog作为归档日志,是mysql原生的日志文件,与引擎层无关,可以用来做主从同步等,所以这里的数据也需要和redolog的日志保持一致,这里可以看下mysql日志的处理流程

这里主要分为几个步骤:

  • 事务开始阶段,根据sql做数据更新,将数据写入redolog,此时数据处于prepare阶段
  • 事务提交时,将数据写入binlog中
  • 将redolog对应数据状态改为commit阶段

两阶段提交

从图中可以看出,日志是需要两阶段提交的,这种处理手法其实类似于分布式事务中的本地消息表,主要是为了解决两个实体间的数据同步问题,也就是解决redolog的数据和binglog的数据可能存在不一致的情况。这里我们可以从异常场景来分析为啥需要两阶段提交

写redolog完成,但是binglog只写入部分就宕机了?

这里由于binlog只写入了部分,所以数据和redolog中存在差异,那么当故障恢复时,redolog恢复了提交的事务数据也就是redolog中的信息,但是由于binglog数据缺失,导致主从同步中少了这部分数据的信息,导致主从数据不一致的场景。

WAL(write-ahead log)

这里为了保障写入的高效性,引入了WAL。WAL关键技术在于先写日志再写磁盘,我理解这么做的好处有两点:

  1. 日志可以是同步写缓存加顺序更新文件,而写磁盘是非顺序型的IO写入,这里写入的成本要比先写日志大的多,这里将随机写转化为顺序写,可以大大提升写入数据的效率
  2. 先写日志有利于实现事务,万一事务执行中途出现失败,可以很方便进行回滚,便于集中对事物的数据进行处理

小结

这里我们来小结下,如何保证数据不丢,那么基于mysql的存储架构,主要实现了

  • 引入redolog,保证写入的事务性,在一个事务流程下,需要同时保证redolog和binlog的数据落盘
  • WAL,保证写入数据一致的同时,提升了数据的写入效率
  • 两阶段提交,保证redolog和binlog的数据一致性

二、如何保证写不错

实现写不错是在写不丢的前提上,保障在并发场景下数据写入的一致性。那我们自然而然的就会想到“锁”。没错,mysql也有对应的锁来保证数据写入的互斥进而数据的一致性,然而这里既有隐式的加锁,也有需要显式加锁的地方,因此我们需要做到心中有数才能更好地保护好我们的数据。下面我们来详细讨论下这几种锁。

mysql的锁

主要分为几种锁

  • 全局锁
  • 表锁
  • 行锁(innodb引擎)
  • 间隙锁(innodb引擎)

全局锁

是对整个数据库实例加锁,mysql提了一个加全局读锁的方法,命令是Flush tables with read lock (FTWRL),当你需要让整个库处于只读状态,当使用该命令的时候,以下语句会被阻塞:

  • 数据更新语句,包含增删改
  • 数据定义语句,包含建表,修改表结构等
  • 更新事务类提交语句,commit等 这里典型的使用场景是做全库逻辑备份,在备份过程中整个库处于只读状态,会影响线上的数据写入

表级锁

表级锁有两种:

  • 表锁
  • 元数据锁

表锁的语法是lock tables … read/write,该操作会限制别的线程对该表的读写。元数据锁不需要显示使用,当对一个表做增删改查操作的时候,加MDL读锁,当要对表结构变更操作的时候,加MDL写锁,这里需要特别注意的是,当我们在线上对数据进行变更的时候,很可能会阻塞其他的读写请求,这时候由于客户端有重试机制,很可能引发雪崩,导致db服务瘫痪

行锁

行锁是针对数据库表中行记录的锁

  • 两阶段锁协议,在innodb事务中,行锁在需要的时候才加上,等到事务结束时释放。事务之间对于同一个行的数据操作应该是互斥的,也就是后一个事务必须等到前一个事务操作完成才能操作该行数据,这就保证了并发场景下数据读写的安全性。
  • 主动加锁,通过select ... for update主动为数据加锁,这里需要注意的是,这里和隔离级别以及索引有关,如果加锁的数据没有索引,则会退化成表锁;如果是可重复读的情况下,大概率会成为next-key lock,具体的情况就不在这里讨论了。

间隙锁

间隙锁是在可重复读的前提下引入的,在数据的间隙范围内加上锁,从而避免出现幻读的场景,保证RR隔离级别下数据写入的一致性,这里涉及到mysql是如何在当前读的场景下解决幻读情况,后续有机会的话详细出一篇讨论这种情况下的各种case。

小结

基于以上的几种锁,能够保障我们数据写入的一致性,我们在这里除了关注数据写入的安全性,也需要关注锁对于程序的性能影响,但是由于这并不是数据可靠性的讨论范畴就不在此赘述了。

三、如何保证高可用

前面我们讨论的写不丢和写不错仅仅是在单实例的维度下确保数据的可靠性,但是在现实的生产过程中情况往往是复杂多变的,假如这时候存储数据的机房炸了,那么这种数据的丢失对于我们来说无疑是灭顶之灾,因此除了在mysql的存储过程以及语法语义上来保证数据写入的可靠性,还需要从部署架构上来考虑数据的可靠性,常见的方法就是做好主备同步,也就是不要把所有鸡蛋放到同一个篮子中

binlog数据格式

在讨论高可用的同步架构前,我们先了解下binlog的格式,因为这是我们做主从同步的数据源,而数据源的可靠性关系到数据的最终一致性,mysql对于binglog提供了三种数据格式:

  • statement
  • row
  • mixed

明明是统一的归档日志,为啥要区分不同的数据格式呢?可以看下表格的分析。

格式定义优点缺点
statement默认的格式,记录sql的修改语句日志文件小,节约IO,提高性能准确性差,对于一些系统函数不能准确复制,如now()、uuid()
row记录每行实际数据的变更准确性强,能够准确复制数据变更日志文件大,较大的磁盘和IO
mixedstatement和row的混合准确性强,文件大小适中可能发生主从不一致问题

生产一般至少需要将格式设置成mixed,row则是推荐的存储格式,牺牲磁盘空间来换取数据的可靠性我认为还是很有必要的,而且当前的机器的性能和磁盘对于这种增量已经完全可以cover住,所以为了保障数据的同步过程中的可靠性,我们还是设置成row格式

主从同步

了解完了binlog的数据格式,我们看下单机情况下的主从同步

mysql的主从复制工作过程:

  • 从库生成两个线程,一个IO线程,一个SQL线程;
  • IO线程去拉主库的binlog,并将得到的binglog日志写到relay log文件中;
  • 主库会生成一个log dump线程,用来给从库IO线程传binlog;
  • SQL线程会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一直而达到最终一致;

从执行过程中我们看到slave节点有个relay log(中继日志),那么为啥还要再写一次中继日志呢,我直接把master拉过来的数据直接在slave db上再执行一次不就行了?这里主要原因是相比于写relay log,执行sql所需的消耗更大,所以我们先将日志记录在relay log上,不会阻塞住正在同步的IO线程,同时异步去通过sql线程来在db上执行这些数据就行啦,所以中继日志主要是为了提高slave节点的同步效率而生的。

复制模式定义优点缺点
异步客户端提交事务在主节点处理完会理解返回,并不会等到客户端同步成功后再返回时耗低,同步不会阻塞主链路可靠性低,如果此时主节点crash,切换节点会存在主从存在数据不一致的场景
全同步客户端提交事务,主节点处理完并同步给从节点,并且从节点执行完事务全部回复ack之后,才返回客户端成功数据可靠性高,主节点和从节点数据保持一致受限于从节点的同步时延,同步效率低
半同步客户端提交事务,主节点处理完并同步从节点,从节点只要有一个节点执行完事务并返回确认ack,就返回客户端成功兼具数据可靠性和同步效率半同步复制在节点同步超时的情况下会退化为异步复制,且在主节点同步宕机的情况下可能会出现事务多次执行的情况

这里联想一下,kafka的同步机制是全同步的方式,但是通过ISR的机制,保证每个节点同步的高效性,并且基于kafka的消息机制,也就是消息都是顺序的,可以通过高低水位来保证数据同步的有效性,又由于是顺序读写,磁盘写入的效率较高。

高可用架构

基于mysql自身的主从同步方案

用户通过VIP访问Master和Slave节点,每个节点采用keepalved探活。配置主从关系,进行数据同步

基于MHA的高可用架构

部署一份MHA的Manager节点,在MySQL各个实例部署MHA Node节点。MHA可以实现秒级的故障自动转移。 当然MySQL节点之间的数据同步还要以来MySQL自身的数据同步方式。

以上只是简单的罗列了目前市面上的几种方案,我理解高可用的架构主要思想主要需要以下几点:

  1. 主从节点进行数据同步
  2. 需要一个代理节点来对集群的情况进行探活和故障自动转移
  3. 主从通过心跳保障连接,一旦发现连通性降低及时剔除同步队列,保障同步的高效性

小结

高可用的实现依赖于数据同步和故障转移,因此首先需要了解下mysql的数据同步格式,因为数据格式影响了数据同步的完整性,基于数据格式再了解相关的同步机制的实现,主要是可靠性和同步效率的一种权衡,最后就是目前市面上的几种同步结构的简单介绍,其核心思想都是同步master和slave节点的同步以及proxy完成故障的迁移。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目:思考与结论:人类成长的两个阶段 在我们的成长过程中,思考和结论是两个至关重要的过程。 小时候,我们对于世界充满好奇心,我们对周围的环境和事物充满探索欲望。我们喜欢提出各种问题,并寻找答案。我们天真地认为一切都有解决方法,只要我们能够找到答案。 随着我们的成长和经验积累,我们渐渐地变得注重结论和结果。在学校里,我们被要求完成各种任务,其目的就是为了要求我们得出正确的结论。我们被教育要追求“唯一正确”的答案,而不是多元化的可能性。我们开始失去我们的好奇心,变得麻木和钝化。 这种转变是自然而然的,由于经验和成熟度的增加,我们的思维变得更加成果导向。我们不再停留在探索的阶段,而是注重寻找答案。在工作和生活中,我们需要面对各种任务和挑战,我们需要得出结论并采取行动。 在这个进程中,我们丧失了思考的能力。我们渐渐地忘记了发问的重要性,忘记了我们曾经的好奇心和探索欲望。有些人甚至认为问问题和思考是浪费时间和资源。此时,我们面临的问题是,我们是否失去了重要的能力? 我们应该寻找一种平衡,既关注思考,也注重结论。好的思考瞄准问题的本质,我们应该寻找事物的更深层次,尝试寻找多种可能性。事实上,不同的人对同一个问题可能有不同的回答,我们应该尊重这样的特点。 在追求结论的过程中,我们应该审慎地选择正确的道路。我们应该采用科学的方法去。“唯一正确”并不一定是最好的答案,我们应该为探索真理而不是为了得到结果而去找答案。 综上所述,思考和结论是人类成长的两个阶段,我们应该在其中找到平衡点。在寻找答案和得出结论的同时,我们也应该保持好奇心和探索欲望,重视思考和探索。这样,我们可以发现真实的答案,并在成长中越来越成熟。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值