摘要
分析主从同步出现的原因,MySQL实现主从同步的原理,思考实现原理的局限性和优点
背景
在实际应用中主从同步常用于实现备份、负载均衡和高可用
。数据冗余的目的是提高数据的安全性,避免因磁盘损坏导致数据丢失的问题。读写分离的目的是减轻单台主机的通信压力,提高系统的吞吐量。一般业务中的读操作要远远大于写操作,使用主从同步将读操作的请求量分散到多个从主机,使每台从主机的压力都变小。
原理
从整体上看MySQL实现主从同步主要有三个步骤:
- 在主库上把数据更改记录到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)。
- 备库将主库上的日志复制到自己的中继日志(Relay Log)中。
- 备库读取中继日志中的事件,将其重放到备库数据之上。
原理分析
保存数据更改事件
在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志
。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。
MySQL实现了两种方式记录数据的更新,分别是基于语句和基于行。基于语句就是记录写操作命令,基于行是记录执行写操作后受影响的行数据。这两种方式都有利弊,实际情况推荐是两种方式混合使用。
基于语句的方式
实现简单,只需要按事务执行顺序把对应的SQL语句记录下来即可。但是对于一些特殊的SQL会无法同步到正确的数据,如SQL中用到了CURRENT_USER()
等与当前环境相关的函数,在从节点重放SQL时可能会得到不一样的数据。
如果正在使用触发器或者存储过程,就不要使用基于语句的复制模式,除非能够清楚地确定不会碰到复制问题。
基于行的方式
将实际数据记录在二进制日志中。这种方式最大的好处是可以正确地复制每一行
。在一些场景下可能没有基于语句高效。如更新所有用户的状态,基于语句的方式只需要记录一条SQL语句,而基于行的方式则需要记录所有更改的用户数据。
两种复制方式的比较
- 复制的准确性:
- 基于行的复制几乎可以准确无误地复制所有数据变更,因为它记录了每一行数据的变化,不受SQL语句的影响。这在处理复杂的SQL语句、存储过程、触发器和用户定义函数时尤其重要,因为这些可能在不同的环境中产生不同的结果。
- 基于语句的复制则可能在某些情况下无法准确复制,比如当复制环境与主库环境不完全相同时,由于SQL语句在从库上的执行可能产生不同的结果。
- 资源消耗:
- 基于行的复制通常会占用更多的日志空间和网络带宽,因为它记录了更多的数据。这在数据变更频繁的场景中尤其明显。
- 基于语句的复制通常消耗较少的资源,因为它只记录执行的SQL语句,这在数据变更较小或网络带宽受限的环境中可能是更好的选择。
- 性能影响:
- 基于行的复制在处理大量数据变更时可能会对主库的性能产生更大影响,因为它需要记录更多的信息。
- 基于语句的复制在执行简单的查询和变更时,通常对性能的影响较小。
- 可追溯性和审计:
- 基于行的复制提供了更好的可追溯性,因为可以明确看到哪些行数据发生了变化。
- 基于语句的复制可能在追踪具体数据变更时不够直观。
- 故障恢复:
- 基于行的复制在故障恢复时可能更容易,因为可以明确知道哪些数据需要被恢复。
- 基于语句的复制可能需要更复杂的故障恢复策略,尤其是当遇到无法正确执行的语句时。
在实际应用中,MySQL从5.1版本开始引入了混合复制模式
(Mixed-Based Replication, MBR)。在这种模式下,MySQL默认尝试使用SBR(基于语句的复制 [Statement-Based Replication]),但在检测到SBR可能失败的情况下自动切换到RBR(基于行的复制 [Row-Based Replication])。这种方式试图平衡资源消耗和复制的准确性,是许多场景下的推荐选择。
主节点与从节点同步数据
首先,备库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程(该线程没有对应的SQL命令),这个二进制转储线程会读取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,直到主库发送信号量通知其有新的事件产生时才会被唤醒,备库I/O线程会将接收到的事件记录到中继日志中。
重放SQL
从节点使用单独的SQL线程重放SQL,线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上I/O线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。
这里是MySQL会经常出现主从延时的关键。因为MySQL主节点是在并发的接收写操作,从节点是单线程方式的恢复数据,当写操作并发高或者有写操作执行慢的时候,就会出现主从延时。
思考
技术的发展是由业务推动的。MySQL最开始是没有主从同步的功能的,随着互联网的发展,对数据冗余的需要和MySQL高性能的需求越来越强,主从同步概念也就出现了。
基于语句的复制(也称为逻辑复制)早在MySQL 3.23版本中就存在,而基于行的复制方式在5.1版本中才被加进来。
主从同步的核心是数据同步。首先想到的应该是同步数据,只同步数据时在某些场景下,同步的代价会比较大。为了实现同步的功能,基于语句的优势就体现出来了。
一项技术从想法到落地是一步步演进的,在演进的过程中会迭代很多次。很多现在用的框架、中间件等都是迭代了很多版本,所以有了想法先能落地是比较重要的,不能纸上谈兵。
MySQL为什么只用一个SQL线程重发SQL语句?只有一个线程重放SQL,在很大程度上总会有延时的。这里我觉得不应该关注在主从延时上。主从同步从理论上说就不可能做到实时,理想情况下也会有几十毫秒的延时。重点应该关注主从同步带来的作用,它实现了数据冗余,提高MySQL的服务能力。
MySQL在5.6版本之后支持了并行复制,使用多条SQL线程重放SQL语句。
现代服务器通常配备多核CPU和高带宽网络,单线程复制模型无法充分利用这些资源。高性能和低延时对于许多现代应用程序至关重要。并行复制能够帮助数据库架构更好地支撑实时数据分析、在线交易处理等高要求场景。
尽管单个SQL线程进行重放有以下几点优势:
- 数据一致性:
单个SQL线程确保所有事件按照它们在主库上的发生顺序执行,这对于依赖于顺序执行的事务非常重要,以维持数据的一致性。 - 事务完整性:
单个SQL线程有助于保证事务的原子性和隔离性,确保从库上的事务执行与主库完全相同。 - 简化故障恢复:
使用单个SQL线程使得故障恢复更加简单,因为不需要处理多个并发SQL线程可能引入的复杂性。 - 资源管理:
单线程设计减少了资源争用,如CPU、内存和磁盘I/O,从而降低了系统开销。
但是软件的发展要紧随市场的脚步,否则就会被淘汰。