MongoDB与MySQL关于写确认的异同

张学伟 2020-05-06

数据存储与数据库 分布式 MongoDB mysql 性能 日志 LOG 数据库 Image 同步 binlog 磁盘 存储 数据一致

MongoDB与MySQL关于写确认的异同

楔子

之前几周有幸被京东智联云的市场同事推荐参与麦思博的一个视频课程的录制,题目是与MongoDB相关的内容。在ppt里也写到了推荐学员可以对比参照其他数据的原理和特点,来学习和理解MongoDB的一些原理和特点,而自己最近在学习的时候,正好发现了一处MongoDB与MySQL设计非常相似的地方,即今天要介绍的写确认相关的内容。

所谓写确认,是指用户将数据写入数据库之后,数据库告知用户写入成功的一个概念。根据数据库的特点和配置,可以在不同的写入程度上,返回给用户,而这其中,就涉及到了不同的性能、数据安全等级以及数据一致性的内容。

而这些不同的写入确认级别或配置,是数据库提供给用户的一种自我控制的能力,用户可以针对自身业务的特点、数据管理的需要、性能的考虑、数据一致性以及服务可用性各种因素进行考虑,选择适合的数据库配置,来实现自身的需要。

概念

首先介绍几个重要的概念,这些概念也是数据库中常识性的知识了;不过是在不同数据库的不同表述。

这些概念主要涉及到写确认的两个重要考量点,一个是本地数据库写操作的不丢失,一个是分布式环境下,数据冗余的一致性。

本地数据库写操作是指数据库在处理用户的写操作后,能够持续化,防止因为意外导致的数据丢失,这个主要涉及到日志,比如MySQL中的redo log和MongoDB中的journal日志。

数据冗余的一致性是指多副本的环境下,比如主从或复制集架构下,数据写入主节点后,如何实现从节点实现与主节点的数据一致,而主从之间是以另外一个日志实现数据同步的,比如MySQL的binlog和MongoDB中的oplog日志。

另外防止主节点崩溃,数据未能同步到从节点,导致从节点成为新的主节点后,未同步数据丢失,也是写确认中重要的内容,即不但同步数据,而且要让数据安全快速的同步。

redo/journal

MySQL的redo log和MongoDB的journal日志都是数据库存储引擎层面的WAL(Write-Ahead Logging)预写式日志,记录的是数据也的物理修改,是提高数据系统持久性的一种技术。

redo log

redo log是MySQL的默认存储引擎innodb事务日志中的核心日志文件之一,俗称重做日志,主要用作前滚的数据恢复。

当我们想要修改MySQL数据库中某一行数据的时候,innodb是把数据从磁盘读取到内存的缓冲池上进行修改。这个时候数据在内存中被修改,与磁盘中相比就存在了差异,我们称这种有差异的数据为脏页。innodb对脏页的处理不是每次生成脏页就将脏页刷新回磁盘,这样会产生海量的io操作,严重影响innodb的处理性能,因此并不是每次有了脏页都立刻刷新到磁盘中。既然脏页与磁盘中的数据存在差异,那么如果在这期间数据库出现故障就会造成数据的丢失。

而redo log就是为了解决这个问题。由于redo log的存在,可以延迟刷新脏页到磁盘的时间,保障了数据库性能的情况下提高了数据的安全。虽然增加了redo log刷新的开销,但是由于redo log采用的顺序io,比数据页的随机io要快很多,这额外的开销可接受。

即,数据库先将数据页的物理修改情况写到刷盘较快的redo log文件中,防止数据丢失。一旦发生故障,数据库重启恢复的时候,可以先从redo log把未刷新到磁盘的已经提交的物理数据页恢复回来。

image

journal

journal是MongoDB存储引擎层面的概念,MongoDB主要支持的mmapv1、wiredtiger、mongorocks等存储引擎,都⽀持配置journal。MongoDB可以基于journal来恢复因为崩溃未及时写到磁盘的信息。

MongoDB 所有的数据写⼊、读取最终都是调存储引擎层的接⼝来存储、读取数据,journal 是存储引擎存储数据时的一种辅助机制。

在MongoDB的4.0版本以前,用户可以设置是否开启journal日志;从4.0版本开始,副本集成员必须开启journal功能。

以wiredtiger为例,如果不配置journal,写入wiredtiger的数据,并不会立即持久化存储;而是每分钟会做一次全量的checkpoint( storage.syncPeriodSecs配置项,默认为1分钟),将所有的数据持久化。如果中间出现宕机,那么数据只能恢复到最近的一次checkpoint,这样最多可能丢掉1分钟的数据。

所以建议「一定要开启journal」,开启journal后,每次写入会记录一条操作日志(通过journal可以重新构造出写入的数据)。这样即使出现宕机,启动时 Wiredtiger 会先将数据恢复到最近的一次checkpoint的点,然后重放后续的 journal操作日志来恢复数据。

image

建议各位看一下这篇文章,mongodb中journal工作原理,也就能明白上边这个图的含义。

binlog/oplog

MySQL的binlog和MongoDB的oplog都是数据库层面的写操作对应的逻辑日志,主要用于实现数据在主动之间的同步复制以及增量备份和恢复。

binlog

binlog是MySQL数据库层面的一种二进制日志,不管底层使用的什么存储引擎,对数据库的修改都会产生这种日志。binlog记录操作的方法是逻辑性语句,可以通过设置log-bin=mysql-bin来启动该功能。

binlog中记录了有关写操作的执行时间、操作类型、以及操作的具体内容,比如SQL语句(statement)或每行实际数据的变更(row)。

image

上图是MySQL主从之间是如何实现数据复制的,其中的三个重要过程是:

  1. 主库(Master)把数据库更改记录到binlog(图中的Binary Log)中;
  2. 备库(Slave)将主库上的binlog复制到自己的中继日志(Relay log)中;
  3. 备库读取中继日志中的事件,将其重放(Replay)到备库数据之上。

这样源源不断的复制,实现了数据在数据库节点之间的一致。

oplog

oplog是MongoDB数据库层面的概念,在复制集架构下,主备节点之间通过oplog来实现节点间的数据同步。Primary中所有的写入操作都会记录到MongoDB Oplog中,然后从库会来主库一直拉取Oplog并应用到自己的数据库中。这里的Oplog是MongoDB local数据库的一个集合,它是Capped collection,通俗意思就是它是固定大小,循环使用的。

oplog 在 MongoDB 里是一个普通的 capped collection,对于存储引擎来说,oplog只是一部分普通的数据而已。

只有按复制集架构启动的节点会自动在local库中创建oplog.rs的集合。

oplog中记录了有关写操作的操作时间、操作类型、以及操作的具体内容,几乎保留的每行实际数据的变更(在4.0及以后版本中,一个事务中涉及的多个文档,会写在一条oplog中)。

image

上图是MongoDB主备之间如何实现数据复制的,其中的四个重要过程是:

  1. 主库(Primary)把数据库更改记录到oplog(图中的Capped Oplog集合)中;
  2. 备库(Secondary)把主库上的oplog拉取到自己的回放队列中(Queue)中;
  3. 备库读取队列中的oplog,批量回放(applyOps)到备库数据中;
  4. 再将队列中的Oplog写入到备库中的oplog.rs集合中。

这样源源不断的复制,实现了数据在数据库节点之间的一致。

另外MongoDB支持链式复制,即oplog不一定从Primary中获取,还可以从其他Secondary获取。

redo与binlog

  1. redo log是在innodb存储引擎层产生,而binlog是MySQL数据库的上层产生的,并且binlog不仅仅针对innodb存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生binlog。
  2. 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句或行的修改内容。而innodb存储引擎层面的redo log是物理日志。
  3. 两种日志与记录写入磁盘的时间点不同,binlog只在事务提交完成后进行一次写入。而innodb存储引擎的redo log在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。

    binlog仅在事务提交时记录,并且对于每一个事务,仅在事务提交时记录,并且对于每一个事务,仅包含对应事务的一个日志。而对于innodb存储引擎的redo log,由于其记录是物理操作日志,因此每个事务对应多个日志条目,并且事务的redo log写入是并发的,并非在事务提交时写入,其在文件中记录的顺序并非是事务开始的顺序。
  4. binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redo log是循环使用。
  5. binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

image

journal与oplog

  1. journal日志是在wiretiger、mmapV1等存储引擎层产生,而oplog是MongoDB数据库的主从复制层面的概念,oplog也与存储引擎无关;
  2. 两种日志记录的内容形式不同。MongoDB的oplog是逻辑日志,其记录的是对应的写操作的内容。而journal存储的物理修改;
  3. 两种日志与记录写入磁盘的时间点不同。

MongoDB 复制集里写入一个文档时,需要修改如下数据

  1. 将文档数据写入对应的集合
  2. 更新集合的所有索引信息
  3. 写入一条oplog用于同步
    最终存储引擎会将所有修改操作应用,并将上述3个操作写⼊到一条 journal 操作日志里。
  1. journal不是循环使用,在写满或者重启之后,会生成新的journal文件,oplog是循环使用;
  2. oplog可以作为恢复数据使用,复制集架构,journal作为一场宕机或者介质故障后的数据恢复使用。

image

写确认

写确认这个概念其实是来自于MongoDB中的write concern,描述的是MongoDB对一个写操作的确认(acknowledgment)等级。而MySQL中对应的这个概念,可以理解为,用户在提交(commit)写操作的时候,需要经过哪些操作之后就会告知用户提交成功。

MongoDB

在MongoDB中,数据库支持基于write concern功能使用户配置灵活的写入策略,则不同的策略对应不同的数据写入程度即返回给用户写入成功,用户可以继续操作下一个写请求。

write concern

write concern支持3个配置项:

{ w: , j: , wtimeout: }

其中:

  1. w,该参数要求写操作已经写入到个节点才向用户确认;

    1. {w: 0} 对客户端的写入不需要发送任何确认,适用于性能要求高,但不关注正确性的场景;
    2. {w: 1} 默认的writeConcern,数据写入到Primary就向客户端发送确认;
    3. {w: "majority"} 数据写入到副本集大多数成员后向客户端发送确认,适用于对数据安全性要求比较高的场景,该选项会降低写入性能;
  2. j,该参数表示是否写操作要进行journal持久化之后才向用户确认;

    1. {j: true} 要求primary写操作进行了journal持久化之后才向用户确认;
    2. {j: false} 要求写操作已经在journal缓存中即可向用户确认;journal后续会将持久化到磁盘,默认是100ms;
  3. wtimeout,该参数表示写入超时时间,w大于1时有效;当w大于1时,写操作需要成功写入若干个节点才算成功,如果写入过程中节点有故障,导致写操作迟迟不能满足w要求,也就一直不能向用户返回确认结果,为了防止这种情况,用户可以设置wtimeout来指定超时时间,写入过程持续超过该时间仍未结束,则认为写入失败。

副本集下的写确认

下面以一个副本集架构来描述,一个写操作的流程,来认识MongoDB下的写确认。
image

上面这个写操作,{w:2},需要至少两个节点写成功才可以返回给用户写成功;而每个节点的写入成功可以基于参数{j}来判断,如果{j:true},则每个节点写入操作的journal都刷盘才可以;如果{j:false},则写入操作的journal在缓存中即可以返回成功;

另外,MongoDB的Primary如何知道Secondary是否已经同步成功呢,是基于如下流程:

  1. Client向Primary发起请求,指定writeConcern为{w: "majority"},Primary收到请求,本地写入并记录写请求到oplog,然后等待大多数节点都同步了这条/批oplog(Secondary应用完oplog会向主报告最新进度);
  2. Secondary拉取到Primary上新写入的oplog,本地重放并记录oplog。为了让Secondary能在第一时间内拉取到主上的oplog,find命令支持一个awaitData的选项,当find没有任何符合条件的文档时,并不立即返回,而是等待最多maxTimeMS(默认为2s)时间看是否有新的符合条件的数据,如果有就返回;所以当新写入oplog时,备立马能获取到新的oplog;
  3. Secondary上有单独的线程,当oplog的最新时间戳发生更新时,就会向Primary发送replSetUpdatePosition命令更新自己的oplog时间戳;
  4. 当Primary发现有足够多的节点oplog时间戳已经满足条件了,向客户端发送确认,这样,Primary即可知道数据已经同步到了。

image

MongoDB写确认行为

可以依据以上概念,可以推导出,在副本集架构下,MongoDB的写确认有如下可能,假设该副本集的可同步数据的节点个数N超过了3个,即majority大于等于3:

参数设置 行为 可能的风险
{w:0, j:false} 无需返回对写操作的确认,即发送写请求后即返回 写是否成功不可知,写可能失败;
写成功后journal未刷新到磁盘,崩溃可能丢失数据;
还可能Primary的journal刷新到了磁盘,但是Secondary还未来得及同步数据,Primary崩溃,
Secondary成为新Primary,旧Primary重新启动后,会导致刚才的写被回滚,以和现在的新Primary保持数据一致,详情可看MongoDB副本集下的 rollback,请注意与MySQL中事务回滚rollback的区别
{w:0, j:true} 由于j:true,此时要求Primary返回写确认,即写操作对应journal刷新到了Primary磁盘 返回写成功后Primary崩溃,可能发生rollback,导致写操作回滚丢失
{w:1, j:false} 写操作的journal在Primary内存中即可返回确认 写成功后journal未刷新到磁盘,崩溃可能丢失数据;
返回写成功后Primary崩溃,可能发生rollback,导致写操作回滚丢失;
还有一种情况,如果Primary的journal未刷盘,但是oplog到了Secondary且回放正确,Primary挂了,Secondary成为新Primary,旧Primary启动后,
{w:1, j:true} 写操作的journal在Primary刷新到了磁盘 返回写成功后Primary崩溃,可能发生rollback,导致写操作回滚丢失
{w:2, j:false} 要求Primary写操作的journal在内存中,且有一个Secondary获取到了oplog,并回放,且该写操作的journal在内存中,才可以返回给用户写成功 返回写成功后,如果此时两个节点都未来得及刷盘且崩溃,则数据丢失;
两个节点刷盘了,但是其他的Secondary未来得及同步数据,则还是可能发生rollback,但此时两个节点同时发生问题的可能性很低
{w:2, j:true} 写操作的journal在Primary刷新到了磁盘,且有一个Secondary同步且写操作到了journal磁盘才能返回用户写成功 两个节点刷盘了,返回写成功后,但是其他的Secondary未来得及同步数据,则还是可能发生rollback,但此时两个节点同时发生问题的可能性很低
{w:"majority", j:false} 写对应的操作已经同步到了大多数节点的内存,才返回给用户写成功 大多数节点同时发生问题的可能性已经极低,除非机架、机房或地域的灾难
{w:"majority", j:true} 写对应的操作已经同步到了大多数节点的journal磁盘,才返回给用户写成功 大多数节点同时发生问题的可能性已经极低,除非机架、机房或地域的灾难
{w:N(所有节点), j:false} 写对应的操作已经同步到了所有节点的内存,才返回给用户写成功 大多数节点同时发生问题的可能性已经极低,除非机架、机房或地域的灾难
{w:N(所有节点), j:true} 写对应的操作已经同步到了所有节点的journal磁盘,才返回给用户写成功 大多数节点同时发生问题的可能性已经极低,除非机架、机房或地域的灾难

注意:MongoDB的rollback未强调写成功数据的rollback,所有写入到了Primary但是未同步到Secondary,然后Primary崩溃,Secondary成为新主,Primary恢复后拥有了Secondary不存在的数据,然后为了与主保持一致,回滚到与新Primary某一个一致的时间点的流程都是rollback。而上边表格中的rollback更多强调的是已经返回写确认或写成功的数据的rollback。

由上表可知,从上到下的写操作越来越安全,但是同时对用户来说,写性能也越来越低。所以需要用户根据自身性能和安全的衡量,来设置合适的write concern。

第三个参数timeout不是不重要,只是不改变大的流程;timeout影响的是Secondary同步慢的时候,最多等待多久的问题,这个也要根据一些客观因素去选择,比如网络、节点机器性能、同步快慢等。

此外,MongoDB还提供了一个read concern的参数,可以控制从复制集或分片集群读取数据的一致性和隔离性。详情见:read concern

MySQL

MySQL数据库在所谓写确认或写成功方面可以通过执行事务的commit提交来体现,提交成功则为写成功。因此我主要从事务在执行事务以及commit事务的过程中,涉及的redo log、binlog以及两种日志的刷盘和主从复制的流程来分析MySQL的写成功相关的设置和问题。

MySQL复制架构

目前MySQL较为流量的版本包括5.5、5.6、5.7、8.0,而8.0版本中使用的Group Replication来实现多节点的数据一致性,这种组复制依靠分布式一致性协议(Paxos协议的变体),实现了分布式下数据的最终一致性。与MySQL其他几个版本和MongoDB的数据一致性有一些区别,故本篇文章暂时不同步分析和对比,对MySQL 8.0版本的组复制有兴趣的同学可参考:

MySQL · 引擎特性 · Group Replication内核解析

组复制背景 | 全方位认识 MySQL 8.0 Group Replication

组复制常规操作-事务一致性保证 | 全方位认识 MySQL 8.0 Group Replication

MySQL中有几种常见复制机制:

  1. 同步复制。当主库提交事务之后,所有的从库节点必须收到、Replay并且提交这些事务,然后主库线程才能继续做后续操作。但缺点是,主库完成一个事务的时间会被拉长,性能降低。
  2. 异步复制。主库将事务 Binlog 事件写入到 Binlog文件中,此时主库只会通知一下 Dump 线程发送这些新的Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。image
  3. 半同步复制。是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全完成并且提交的反馈,如此,节省了很多时间。image
  4. 组复制。由若干个节点共同组成一个复制组,一个事务的提交,必须经过组内大多数节点(N / 2 + 1)决议并通过,才能得以提交。比如由3个节点组成一个复制组,Consensus层为一致性协议层,在事务提交过程中,发生组间通讯,由2个节点决议(certify)通过这个事务,事务才能够最终得以提交并响应。image

除了组复制,半同步复制技术是性能和安全相对更好的设计,尤其在5.7版本中,优化了之前版本的半同步复制相关的逻辑,因此我们主要以5.7版本来介绍。

MySQL5.6/5.5半同步复制的原理:提交事务的线程会被锁定,直到至少一个Slave收到这个事务,由于事务在被提交到存储引擎之后才被发送到Slave上,所以事务的丢失数量可以下降到最多每线程一个。因为事务是在被提交之后才发送给Slave的,当Slave没有接收成功,并且Master挂了,会导致主从不一致:主有数据,从没有数据。这个被称为AFTER_COMMIT。

MySQL5.7在Master事务提交的时间方面做了改进,事务是在提交之前发送给Slave(AFTER_SYNC),当Slave没有接收成功,并且Master宕机了,不会导致主从不一致,因为此时主还没有提交,所以主从都没有数据。image

不过假如Slave接收成功,并且Master中的binlog未来得及刷盘并且在存储引擎提交之前宕机了,那么很明显这个事务是不成功的,但由于对应的Binlog已经做了Sync操作,从库已经收到了这些Binlog,并且执行成功,相当于在从库上多了数据,也算是有问题的,但多了数据,问题一般不算严重。此时可能就需要8.0版本中的组复制了。

redo log与binlog顺序一致性

另外,redo log与binlog的提交也是有先后顺序的,这是为了保证这两个数据的一致性,这就是MySQL的内部分布式事务:基于2pc(两阶段提交)的redo log与binlog的顺序一致性问题。

MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,而主库binlog写入在commit之前,如果写完binlog主库crash,再次启动时会回滚事务。但此时从库已经执行,则会造成主备数据不一致)。所以在开启Binlog后,如何保证binlog和InnoDB redo日志的一致性呢?为此,MySQL引入二阶段提交(two phase commit or 2pc),MySQL内部会自动将普通事务当做一个XA事务(内部分布式事物)来处理:

  • 自动为每个事务分配一个唯一的ID(XID)。
  • COMMIT会被自动的分成Prepare和Commit两个阶段。
  • Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。

Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:
image

以上的图片中可以看到,事务的提交主要分为两个主要步骤:

  1. 准备阶段(Storage Engine(InnoDB) Transaction Prepare Phase)

此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成第一阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。

  1. 提交阶段(Storage Engine(InnoDB)Commit Phase)

    1. 记录协调者日志,即Binlog日志。
      如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid方法将SQL语句写到binlog(write()将binary log内存日志数据写入文件系统缓存,fsync()将binary log文件系统缓存日志数据永久写入磁盘)。此时,事务已经铁定要提交了。否则,调用ha_rollback_trans方法回滚事务,而SQL语句实际上也不会写到binlog。
    2. 告诉引擎做commit。
      最后,调用引擎的commit完成事务的提交。会清除undo信息,刷redo日志,将事务设为TRX_NOT_STARTED状态。

由上面的二阶段提交流程可以看出,一旦步骤2中的操作完成,就确保了事务的提交,即使在执行步骤3时数据库发送了宕机。此外需要注意的是,每个步骤都需要进行一次fsync操作才能保证上下两层数据的一致性。步骤2的fsync参数由sync_binlog=1控制,步骤3的fsync由参数innodb_flush_log_at_trx_commit=1控制,俗称“双1”,是保证CrashSafe的根本。

为了保证数据的安全性,以上列出的3个步骤都需要调用fsync将数据持久化到磁盘。由于在引擎内部prepare好的事务可以通过binlog恢复,所以通常情况下第三个fsync是可以省略的。

MySQL内部两阶段提交需要开启innodb_support_xa=true,默认开启。

更详细内容见:MySQL 中Redo与Binlog顺序一致性问题 【转】

刷盘相关的配置项

innodb_flush_log_at_trx_commit

  1. 如果innodb_flush_log_at_trx_commit设置为0:log buffer将每秒一次地写入log file中,并且log file的flush(刷到磁盘)操作同时进行.该模式下,在事务提交的时候,不会主动触发写入磁盘的操作;此时mysqld进程的崩溃会导致上一秒钟所有事务数据的丢失;
  2. 如果innodb_flush_log_at_trx_commit设置为1:每次事务提交时MySQL都会把log buffer的数据写入log file,并且flush(刷到磁盘)中去;
  3. 如果innodb_flush_log_at_trx_commit设置为2:每次事务提交时MySQL都会把log buffer的数据写入log file,但是flush(刷到磁盘)操作并不会同时进行。该模式下,MySQL会每秒执行一次 flush(刷到磁盘)操作。此时只有在操作系统崩溃或者系统掉电的情况下,上一秒钟所有事务数据才可能丢失。
    image

请注意0和2的区别,0是将redo log写到了Log Buffer,用户态缓存;2是写到了系统内核态缓存,所以对应的崩溃情形不一样。

sync_binlog

  1. sync_binlog 的默认值是0,像操作系统刷其他文件的机制一样,MySQL不会同步到磁盘中去而是依赖操作系统来刷新binary log。
  2. 当sync_binlog =N (N>0) ,MySQL 在每写 N次 二进制日志binary log时,会使用fdatasync()函数将它的写二进制日志binary log同步到磁盘中去。

rpl_semi_sync_master_wait_for_slave_count

  1. 该变量控制slave应答的数量,默认是1,表示master接收到几个slave应答后才commit。在多从的环境下,设置大于1可以提高数据的可靠性。

sync_relay_log

  1. 当设置为1时,slave的I/O线程每次接收到master发送过来的binlog日志都要写入系统缓冲区,然后刷入relay log中继日志里,这样是最安全的,因为在崩溃的时候,你最多会丢失一个事务,但会造成磁盘的大量I/O。
  2. 当设置为0时,并不是马上就刷入中继日志里,而是由操作系统决定何时来写入,虽然安全性降低了,但减少了大量的磁盘I/O操作。这个值默认是0,可动态修改,建议采用默认值。

MySQL写确认行为

我们以MySQL的5.7版本的半同步复制的主从架构的为例子,来介绍MySQL各个参数对写确认即commit的不同影响。

image

上图中,能够体现半同步复制(AFTER SYNC)的过程为:

  • 第6步,写binlog;(sync_binlog参数在此起作用)
  • 第7步,同步binlog到备库的Relay log;(sync_relay_log参数在此起作用)
  • 第8步,返回给主库ack;
  • 第9步和第10步,提交事务,将redo log中该事务标记为已提交;(innodb_flush_log_at_trx_commit参数在此起作用)
  • 第11步,返回给用户写成功;

上图中,能体现redo log和binlog顺序一致性的过程为:

  • 第4步,将redo log置为prepare并刷盘;
  • 第6步,写入binlog;(sync_binlog参数在此起作用)
  • 第9步和第10步,提交事务,并将redo log置为提交状态;

下面将把上面介绍的与刷盘有关的配置项引入这整个过程,来看写操作不同的行为和风险。

参数设置 行为 可能的风险
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = 0/N(非1)
sync_relay_log = 0
redo log、sync_binlog、relay_log都不需要刷盘即可以完成数据的提交和同步 在返回已提交成功后,由于未刷盘,如果master产生崩溃,导致binlog丢失,则会回滚若干事务,如果relay log也崩溃,则发生数据丢失;
如果relay log未丢失,则会出现主从数据不一致;
如果master未崩溃,slave崩溃,则主从数据不一致;
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = 0/N(非1)
sync_relay_log = 1
redo log、sync_binlog不需要刷盘,relay log刷盘,完成数据的提交和同步 在返回已提交成功后,由于未刷盘,如果master产生崩溃,导致binlog丢失,则会回滚该事务,则会出现主从数据不一致
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = 1
sync_relay_log = 0
redo log、relay log不需要刷盘,sync_binlog刷盘,完成数据的提交和同步 在返回已提交成功后,由于未刷盘,如果master产生崩溃,事务可通过binlog恢复,但是如果slave也崩溃丢失relay log,则会导致主从数据不一致
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = 1
sync_relay_log = 1
redo log不需要刷盘,sync_binlog、relay log刷盘,完成数据的提交和同步 在返回已提交成功后,就算redo log丢失已提交信息,但是此时可通过binlog恢复,且slave也已同步,这时候数据已提交的情况下,不丢数且可实现主从数据一致性
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = N
sync_relay_log = 0
redo log不需要刷盘,sync_binlog、relay log刷盘,完成数据的提交和同步 在返回已提交成功后,发生崩溃,可能会丢失过去N个事务的数据,如果slave也已同步,会导致主从数据一致性;
如果slave也崩溃,则会导致数据丢失
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = N
sync_relay_log = 1
redo log不需要刷盘,sync_binlog、relay log刷盘,完成数据的提交和同步 在返回已提交成功后,发生崩溃,可能会丢失过去N个事务的数据,如果slave已同步,会导致主从数据一致性
innodb_flush_log_at_trx_commit = 0/1/2
sync_binlog = N
sync_relay_log = 1
redo log不需要刷盘,sync_binlog、relay log刷盘,完成数据的提交和同步 在返回已提交成功后,发生崩溃,可能会丢失过去N个事务的数据,如果slave已同步,会导致主从数据一致性

注意:以上是在开始内部两阶段提交的流程,即innodb_support_xa=true,这个时候可以通过判断binlog来恢复会提交的事务,因此innodb_flush_log_at_trx_commit看起来可有可无;如果未开启内部事务的两阶段提交,则更会复杂,只有innodb_flush_log_at_trx_commit = 1 且 sync_binlog = 1 且 sync_relay_log = 1的情况下,可以保证已提交事务的安全,其他情况都有可能导致数据丢失或者主从数据不一致的风险。

但是在innodb_flush_log_at_trx_commit = 1 且 sync_binlog = 1 且 sync_relay_log = 1 的情况下,MySQL的性能相对最低。可以在提高性能的情况下,比如 innodb_flush_log_at_trx_commit = 2 且 sync_binlog = N (N为500 或1000),由于这种情况,redo log和binlog都在系统缓存中,可以使用带蓄电池后备电源的缓存cache,防止系统断电异常。

此外,rpl_semi_sync_master_wait_for_slave_count参数是控制同步到多少个节点的,类似MongoDB中write concern中的 w 参数,如果这个参数设置为0(其实不能,最低1),则变为了纯粹的异步复制;如果这个参数设置为最大(所有从节点个数),则变为了纯粹的同步复制,因此这个地方也可以根据需要来进行调整,来提交数据的安全。

此外,有可能影响同步模式的还包括rpl_semi_sync_master_wait_no_slave参数、影响复制等待超时的参数rpl_semi_sync_master_timeout等。当rpl_semi_sync_master_wait_no_slave为OFF时,只要master发现Rpl_semi_sync_master_clients小于rpl_semi_sync_master_wait_for_slave_count,则master立即转为异步模式;如果为ON时,如果在事务提交阶段(master等待ACK)超时rpl_semi_sync_master_timeout,master会转为异步模式。

对比

配置比较

比较项 异步刷盘 同步刷盘
journal {j: false},默认每100ms刷盘一次,可修改 {j: true}},journal直接同步刷盘到物理文件
redo log innodb_flush_log_at_trx_commit = 0/2, 每秒刷盘一次 innodb_flush_log_at_trx_commit = 1,直接刷盘到物理文件
比较项 异步刷盘 同步刷盘
oplog oplog数据也保存在journal中,{j: false},默认每100ms刷盘一次,可修改。真正的oplog的数据页在60s之后执行checkpoint,刷新到磁盘 {j: true}},journal直接同步刷盘到物理文件。 真正的oplog的数据页在60s之后执行checkpoint,刷新到磁盘
binlog sync_binlog = 0,像操作系统刷其他文件的机制一样,MySQL不会同步到磁盘中去而是依赖操作系统来刷新binlog; sync_binlog = N(N大于1),每写N次事务的binlog,再将binlog持久化到磁盘 sync_binlog = 1,每次事务的binlog都直接持久化到磁盘
比较项 同步复制 异步复制 半同步复制
MongoDB {w:N(所有可同步数据节点)} {w:0} {w:n(n大于0,小于N)}或{w:"majority"
MySQL rpl_semi_sync_master_wait_for_slave_count = N(所有从节点) 触发异步较复杂 rpl_semi_sync_master_wait_for_slave_count = n(1到N-1)

其他

虽然MongoDB和MySQL在很多方面可以有类似或相似的设置,但是还是存在一些区别,比如:

  1. MongoDB的journal中包含了oplog的信息;而binlog和redo log是两个相对独立的内容;
  2. MySQL几乎所有的写操作都是基于事务来提交的;而MongoDB在4.0开始支持多文档的事务,单文档的事务基于内部事务逻辑实现,未直接提供给用户;
  3. MySQL的写确认是已commit提交成功为标志,MongoDB的普通写操作是返回写入成功为标志;事务写操作也是已commit为标志;

总结

本文章所介绍的写确认的概念,涉及到了MongoDB与MySQL的日志文件(redo log/journal)、同步用日志(binlog/oplog)、刷盘机制和时机、主从同步架构等多个流程和模块,目的就是实现写操作的原子性、持久性、分布式环境下的数据一致性等,对数据的性能和安全都有影响,需要根据数据、业务、压力、安全等客观因素去调整。

由于涉及的内容非常多,未对所有的情况进行测试验证,可能有疏漏或错误,希望各位不吝赐教。

希望本篇文章对于MySQL和MongoDB都有兴趣的同学可以作为一个总结和参考,希望对各位有帮助。

参考

期间参考的资料较多,对未能提及的文章表示歉意

高性能MySQL

MongoDB官方手册

深入浅出MongoDB复制

mysql基于binlog的复制

MongoDB journal 与 oplog,究竟谁先写入?

MySQL5.7新特性--官方高可用方案MGR介绍

MongoDB writeConcern原理解析

mysql日志系统之redo log和bin log

MySQL 5.7 半同步复制增强【转】

MySQL 中Redo与Binlog顺序一致性问题 【转】

详细分析MySQL事务日志(redo log和undo log)

MySQL 的"双1设置"-数据安全的关键参数(案例分享)

rpl_semi_sync_master_wait_no_slave 参数研究实验

MySQL5.7新特性半同步复制之AFTER_SYNC/AFTER_COMMIT的过程分析和总结

公众号

登录 后评论
下一篇
弹性计算秉林
27人浏览
2020-07-08
相关推荐
PHP需要学习成长路径
1635人浏览
2017-08-21 16:10:00
PHP程序员的技术成长规划
1917人浏览
2017-08-01 12:27:00
MongoDB与内存
757人浏览
2015-05-18 10:07:00
Docker with Spring Boot
8346人浏览
2016-06-07 11:11:34
Docker with Spring Boot
2156人浏览
2016-06-07 11:12:52
近20天学习计划的完成情况
1026人浏览
2016-05-14 23:33:54
28个MongoDB 的问题
1134人浏览
2015-01-06 16:21:00
<图解>MongoDB快速入门
639人浏览
2017-08-27 22:31:00
DTS 搬站的实战心得
3885人浏览
2019-07-08 09:14:46
如何成为一名优秀的DBA
488人浏览
2020-03-26 10:47:04
1
0
0
358