MySQL 复制原理详解

作者:谭其文

导语

mysql作为一个开源的数据库,有着广泛的应用。本文主要讲述了mysql复制的原理,以及异步复制,同步复制和并行复制。

1、mysql复制的原理

Mysql有两种复制原理:基于行的复制和基于语句的复制。最早出现的是基于语句的复制,而基于行的复制方式在5.1版本中才被引入。这两种方式都是通过在主库上记录二进制日志(binlog)、在备库上重放日志的方式来实现异步的数据复制。这意味着、在同一时间点备库上的数据可能和主库不一致,并且无法保证主库备库之间的延迟。

1.1 、基于语句的复制

基于语句的复制模式下,主库会记录那些造成数据更改的查询,当备库读取并重放这些事件时,实际上只把主库上执行过的SQL再执行一遍。

优点:

最明显的好处是实现相当简单。理论上讲,简单地记录和执行这些语句,能够让备库保持同步。另外好处是binlog日志里的事件更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。一条更新好几兆数据的语句在二进制日志里可能只占用几十字节。

缺点:

有些数据更新语句,可能依赖其他因素。例如,同一条sql在主库和备库上执行的时间可能稍微或很不相同,因此在传输的binlog日志中,除了查询语句,还包括一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL,例如,使用CURRENT_USER()函数语句。存储过程和触发器在使用基于语句的复制模式时也可能存在问题。另外一个问题是更新必须是串行的。这需要更多的锁。并且不是所有的存储引擎都支持这种复制模式。

1.2、基于行的复制

MySQL5.1开始支持基于行的复制,这种方式会将实际数据记录在二进制日志中,跟其他数据库的实现比较相像。

优点:

最大的好处是可以正确的复制每一行,一些语句可以呗更加有效地复制。由于无需重放更新主库数据的查询,使用基于行的复制模式能够更高效地复制数据。重放一些查询的代价会很高。例如,下面有一个查询将数据从一个大表中汇总到小表


mysql> INSERT INTO summary_table(col1,col2,sum_col3)

         ->SELECT col1,col2,sum(col3) from enormous_table GROUP BY col1,col2;

缺点:

但是另外一方面,下面这条语句使用基于语句的复制方式代价会小很多:

mysql> UPDATE enormous_table SET col1 =0 ;

由于这条语句做了全表更行,使用基于行的复制开销会大很多,因为每一行的数据都会呗记录到二进制日志中,这使得二进制日志时间非常庞大。另外由于语句并没有在日志里记录,因此无法判断执行了哪些sql,除了需要知道行的变化外,这在很多情况下很重要。执行基于行的过程像一个黑盒子,你无法知道服务器正在做什么。

由于没有哪一种模式对所有情况都是完善的,mysql能够在这两种复制模式间动态切换。默认情况下使用的是基于语句的复制方式,但如果发现语句无法呗正确地复制,就切换基于行的复制模式。还可以根据需要来设置会话级别的变量binlog_format,控制二进制日志格式。

2、异步复制过程

总体来说,复制有3个步骤:

1、主服务器把数据更改记录到二进制日志中。(这叫做二进制日志事件)

2、从服务器把主服务器的二进制日志拷贝到自己的中继日志中。

3、从服务器重放中继日志中的事件,把更改应用到自己的数据上。

这只是概述,每一个步骤都很复杂。下图更清晰描述了复制的过程。

第一步、在主服务器上记录二进制日志。在每个更新数据的事务完成之前,主服务器都会将数据更改记录到二进制日志中。即使事务在执行期间是交错的,mysql也会串行地将事务写入到二进制日志中。在把事件写入二进制日志之后,主服务器告诉存储引擎提交事务。

第二步、从服务器把主服务器的二进制日志拷贝到自己的硬盘上,进入所谓的“中继日志”中。首先,它启动一个工作线程,叫I/O线程,这个I/O线程开启一个普通的客户端连接,然后启动一个特殊的二进制日志转储进程(它没有相应的SQL命令)。这个转储进程从主服务器的二进制日志中读取数据。它不会对事件进行轮询。如果3跟上了主服务器,就会进入休眠状态并等待有新的事件发生时主服务器发出的信号。I/O线程把数据写入从服务器的中继日志中。

第三步、SQL线程读取中继日志,并且重放其中的事件,然后更新从服务器的数据。由于这个线程能跟上I/O线程,中继日志通常在操作系统的缓存中,所以中继日志的开销很低。SQL线程执行事件也可以被写入从服务器自己的二进制日志中,它对于有些场景很实用。

3、半同步复制

一般情况下,异步复制就已经足够应付了,但由于是异步复制,备库极有可能是落后于主库,特别是极端情况下,我们无法保证主备数据是严格一致的。比如,当用户发起commit命令时,Master并不关心Slave的执行状态,执行成功后,立即返回给用户。试想下,若一个事务提交后,Master成功返回给用户后crash,这个事务的binlog还没来得及传递到Slave,那么Slave相对于Master而言就少了一个事务,此时主备就不一致了。对于要求强一致的业务是不可以接受的,半同步复制就是为了解决数据一致性而产生的。

为什么叫半同步复制?先说说同步复制,所谓同步复制就是一个事务在Master和Slave都执行后,才返回给用户执行成功。这里核心是说Master和Slave要么都执行,要么都不执行,涉及到2PC(2 Phrase Commit)。而MySQL只实现了本地redo-log和binlog的2PC,但并没有实现Master和Slave的2PC,所以不是严格意义上的同步复制。而MySQL半同步复制不要求Slave执行,而仅仅是接收到日志后,就通知Master可以返回了。这里关键点是Slave接受日志后是否执行,若执行后才通知Master则是同步复制,若仅仅是接受日志成功,则是半同步复制。对于Mysql而言,我们谈到的日志都是binlog,对于其他的关系型数据库可能是redo log或其他日志。

半同步复制如何实现?半同步复制实现的关键点是Master对于事务提交过程特殊处理。目前实现半同步复制主要有两种模式,AFTER_SYNC模式和AFTER_COMMIT模式。两种方式的主要区别在于是否在存储引擎提交后等待Slave的ACK。先来看看AFTER_COMMIT模式,如下图,Start和End分别表示用户发起Commit命令和Master返回给用户的时间点,中间部分就是整个Commit过程Master和Slave做的事情。

Master提交时,会首先将该事务的redo log刷入磁盘,然后将事务的binlog刷入磁盘(这里其实还涉及到两阶段提交的问题,这里不展开讲),然后进入innodb commit流程,这个步骤主要是释放锁,标记事务为提交状态(其他用户可以看到该事务的更新),这个过程完成后,等待Slave发送的ACK消息,等到Slave的响应后,Master才成功返回给用户。看到图中红色虚线部分,这段是Master和Slave的同步逻辑,是Master-Slave一致性的保证。

半同步复制是否能保证不丢数据?我们通过几种场景来简单分析下。第一种情况:假设Master第1,2步执行成功后,binlog还没来得及传递给Slave,此时Master挂了,Slave作为新Master提供服务,那么备库比主库要少一个事务(因为主库的redo 和binlog已经落盘),但是不影响用户,对于用户而言,这个事务没有成功返回,那么提交与否,用户都可以接受,用户一定会进行异常捕获而重试。第二种情况,假设第3步innodb commit执行成功后,binlog还没来得及传递给Slave,此时Master挂了,此时与第一种情况一样,备库比主库少一个事务,但是其他用户在3执行完后,可以看到该事务的更新,而切换到备库后,却发现再次读这个更新又没了,这个就发生了“幻读”,如果其他事务依赖于这个更新,则会对业务逻辑产生影响。当然这仅仅是极端情况。

对于第二种情况产生的影响,AFTER_SYNC模式可以解决这一问题。与AFTER_COMMIT相比,master在AFTER_SYNC模式下,Fsync binlog后,就开始等待SLAVE同步。那么在进行第5步innodbcommit后,即其它事务能看到该事务的更新时,Slave已经成功接收到binlog,即使发生切换,Slave拥有与Master同样的数据,不会发生“幻读”现象。但是对于上面描述的第一种情况,结果是一样的。

所以,在极端情况下,半同步复制的Master-Slave会有一个事务不一致,但是对于用户而言,由于这个事务并没有成功返回给用户,所以无论事务提交与否都是可以接受的,用户有必要进行查询或重试,判读是否更新成功。或者我们想想,对于单机而言,若事务执行成功后,返回给用户时,网络断了,用户也是面临一样的问题,所以,这不是半同步复制的问题。对于提交返回成功的事务,版同步复制保证Master-Slave一定是一致的,从这个角度来看,半同步复制不会丢数据,可以保证Master-Slave的强一致性。下图是AFTER_SYNC模式,事务提交过程。

4、并行复制

半同步复制解决了Master-Slave的强一致问题,那么性能问题呢?从图1中可以看到参与复制的主要有两个线程:IO线程和SQL线程,分别用于拉取和回放binlog。对于Slave而言,所有拉取和解析binlog的动作都是串行的,相对于Master并发处理用户请求,在高负载下, 若Master产生binlog的速度超过Slave消费binlog的速度,导致Slave出现延迟。如下图,可以看到,Users和Master之间的管道远远大于Master和Slave之间的管道。

那么如何并行化,并行IO线程,还是并行SQL线程?其实两方面都可以并行,但是并行SQL线程的收益更大,因为SQL线程做的事情更多(解析,执行)。并行IO线程,可以将从Master拉取和写Relay log分为两个线程;并行SQL线程则可以根据需要做到库级并行,表级并行,事务级并行。库级并行在mysql官方版本5.6已经实现。如下图,并行复制框架实际包含了一个协调线程和若干个工作线程,协调线程负责分发和解决冲突,工作线程只负责执行。图中,DB1,DB2和DB3的事务就可以并发执行,提高了复制的性能。有时候库级并发可能不够,需要做表级并发,或更细粒度的事务级并发。

并行复制如何处理冲突?并发的世界是美好的,但不能乱并发,否则数据就乱了。Master上面通过锁机制来保证并发的事务有序进行,那么并行复制呢?Slave必需保证回放的顺序与Master上事务执行顺序一致,因此只要做到顺序读取binlog,将不冲突的事务并发执行即可。对于库级并发而言,协调线程要保证执行同一个库的事务放在一个工作线程串行执行;对于表级并发而言,协调线程要保证同一个表的事务串行执行;对于事务级而言,则是保证操作同一行的事务串行执行。

是否粒度越细,性能越好?这个并不是一定的。相对于串行复制而言,并行复制多了一个协调线程。协调线程一个重要作用是解决冲突,粒度越细的并发,可能会有更多的冲突,最终可能也是串行执行的,但消耗了大量的冲突检测代价。

外部引用 : 《高性能MYSQL》

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏架构师之路

mysql-proxy数据库中间件架构 | 架构师之路

一、mysql-proxy简介 mysql-proxy是mysql官方提供的mysql中间件服务,上游可接入若干个mysql-client,后端可连接若干个my...

4076
来自专栏数据库架构设计

mysql高可用架构设计

主要介绍:复制功能介绍,mysql二进制日志,mysql复制拓扑,高可用框架,单点故障,读写分离和负载均衡

720
来自专栏gaoqin31

PHP使用守护进程处理队列

  项目是棋牌,web架构是典型的lnmp,server产生的牌局通过http协议请求webserver,由php分析并持久化到mysql,中间参杂了很多业务逻...

1227
来自专栏PHP实战技术

mysql高可用架构设计,处理高并发,大流量!

主要介绍:复制功能介绍、mysql二进制日志、mysql复制拓扑、高可用框架、单点故障、读写分离和负载均衡介绍等 mysql复制功能介绍 mysql复制功能提供...

3657

MySQL集群:高可用性DBMS

如果你在寻找一个不会发生单点故障的数据库管理系统,那么水平拓展的MySQL集群分布式多主架构将是您的最佳选择。MySQL集群可以通过MySQL和...

2407
来自专栏哲学驱动设计

Rafy 框架 - 幽灵插件(假删除)

Rafy 框架又添新成员:幽灵插件。本文将解释该插件的场景、使用方法、原理。 场景 在开发各类数据库应用系统时,往往需要在删除数据时不是真正地删除数据,而只是把...

1998
来自专栏扎心了老铁

记录一次因为硬盘写满造成的redis无法连接

缘起: 今天早晨收到报警,服务不干活了,赶紧起来看问题。。。 为了尽快让服务可用,尝试重启服务,发现服务起不来,报错 redis connection fail...

3428
来自专栏鬼谷君

redis安装配置

1032
来自专栏LuckQI

Java大数据学习~Hadoop初识一了解其架构

723
来自专栏java工会

如何恢复oracle中误删除表(drop掉的)?

以下是一个不该发生却经常发生的情况:用户删除了一个非常重要的表 ― 当然是意外地删除 ― 并需要尽快地恢复。(在某些时候,这个不幸的用户可能就是 DBA!)

652

扫码关注云+社区