MySQL 主从复制
MySQL内建的复制功能是构建基于MySQL的大规模、高性能应用的基础 ,这是MySQL“水平扩展”架构的一种。我们可以通过为主服务器配置n个从库的方式来实现主从间的数据同步。复制功能不仅仅有利于构建高性能的应用,同时也是高可用、高扩展性、灾难恢复、备份以及数据仓库等工作的基础。
复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。
现有MySQL主从复制的方式都是在主库上记录二进制日志、在从库重放日志的方式来实现异步的数据复制。因为是异步复制,就意味着在同一时间点从库上的数据可能与主库存在不一致,并且无法保证主备之间的延迟。一些大的语句可能导致备库产生几秒、几分钟甚至几个小时的延迟。
MySQL复制大部分都是向后兼容的,而无法实现向前兼容。也就是,新版本的服务器可以作为老版本服务器的从库;但翻过来,将老版本作为新版本服务器的从库通常是不可行的,因为旧版本无法解析新版本所采用的新的特性或语法,另外所使用的二进制文件的格式也可能不相同。
复制通常不会增加主库的开销,主要是启用二进制日志带来的开销,但出于备份以及灾难恢复的目的,这点开销也是必要的。除此之外,每个备库也会对主库增加一些负载(例如网络I/O开销),尤其当备库请求主库读取旧的二进制日志文件时,可能会造成更高的I/O开销。另外锁竞争也可能阻碍事务的提交。最后,如果是从一个高吞吐量(例如5000或更高的TPS)的主库上复制到多个备库,唤醒多个复制线程发送事件的开销将会累加。
总而言之:
- MySQL复制功能可以实现MySQL的“水平扩展”。
- 主从间数据同步可以有助于MySQL的高可用、灾后数据恢复、数据仓库等工作。
- 所谓复制简单来说 就是让一台服务器的数据变更同步到其他服务器。
- 现有MySQL主从复制方式都是通过在主库上记录binlog,在从库上重放的方式来实现的。
- 异步复制的方式,会导致在某个时间点上产生主从数据不一致性。(从库缺失数据)
数据库复制所能解决的问题
- 数据分布:异地数据存储。
- 负载均衡:读写分离,多台从库之间可以通过硬编码或使用DNS轮询(将一个机器名指向多个IP地址)再或者Linux虚拟服务器(Linux Virtual Server , LVS)实现负载均衡。
- 备份: 数据备份。
- 高可用性和故障切换:避免单点故障,故障切换。
- MySQL升级测试:这种做法比较普遍,使用一个更高版本的MySQL作为备库,保证升级全部实例前,查询能够在备库按照预期执行。
MySQL复制过程
从上图,我们可以知道,MySQL复制主要有三个步骤:
- 在主库上把数据更改记录到二级制日志(Binar Log)中(这些记录被称为二进制日志事件)。
- 从库将主库上的日志复制到自己的中继日志(RelayLog)中。
- 从库读取中继日志中的事件,将其重放到从库数据之上。
细说:
第一步是在主库上记录二进制日志。在每次MySQL准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。
第二步,从库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程,这个二进制转储线程会读取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上主库,它将进入休眠状态,直到主库发送信号量通知其有新的事件产生时才会被唤醒,从库I/O线程会将接收到的事件记录到中继日志中。
第三步,从库SQL线程执行最后一步,该线程从中继日志中读取事件并在从库执行,从而实现从库数据的更新。当SQL线程追赶上I/O线程时,中继日志通常已经全部在系统缓存中,所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中。
这种架构实现了获取事件和重放事件的解耦,但是也限制了整个复制过程只能串行化执行,因为只有一个SQL线程来重放中继日志中的事件。
MySQL复制原理
基于语句的复制
基于语句的复制就是 主库会记录那些造成数据更改的语句,当备库读取并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。
优点:
一个好处是简单。 理论上讲,简单地记录和执行这些语句,能够让主从保持同步。另一个好处是二进制日志里事件更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。一条更新好几兆数据的语句在二进制日志里可能只占几十个字节。
缺点:
在主库上执行的数据更新操作可能还依赖于其他元素。例如,同一条SQL在主库和备库上执行的时间可能稍微或很不相同,因此在传输的二进制日志中,除了执行的SQL外,还必须包含了一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL。例如: 使用了CURRENT_USER()函数的语句。存储过程和触发器在使用基于语句的复制模式时也可能存在一些问题。
另外一个问题是更新必须是串行化的。这需要更多的锁 —— 有时候要特别关注这一点。
基于行的复制
基于行的复制,这种方式会将实际增删改数据记录在二进制日志中,跟其他数据库的实现比较相像。
这种方式最大的好处是可以正确地复制每一行。并且由于无须重放更新主库数据的全部操作过程,使用基于行的复制模式能够更高效地复制数据,特别是一些增删改查代价很高的语句。比如: 从一个表汇总数据到另一个数据表的全过程。
但在另一方面,如果是批量操作等(比如批量更新),使用基于行的复制开销会很大,因为涉及到的每一行数据都会被记录到二进制日志中,这使得二进制日志事件非常庞大。并且会给主库上记录日志和复制增加额外的负载,更慢的日志记录则会降低并发度。
混合型复制
由于没有哪种模式对所有情况都是完美的,MySQL能够在这两种复制模式间动态切换。默认情况下使用的是基于语句的复制方式,但如果发现语句无法被正确地复制,就切换到基于行的复制模式。 还可以根据需要来设置会话级别变量binlog_format,控制二进制日志格式。