一文聊透MySQL主从复制(建议收藏)

108 阅读6分钟

MySQL的主从复制很多同学都听说过,但熟悉其核心原理的同学却不多,本文就来全面系统地讲述一下。

MySQL的主从复制的作用是,将主库上的DDL、DML变更操作发送到一个或多个从库上,以此实现读写分离、故障转移、数据备份等,起到分散数据库的读写压力、提升可用性、进行故障恢复的作用。

核心原理

很多面试官会在技术面试中问这样一个问题:“MySQL的主从复制是推模式还是拉模式?”

我们来看一下,MySQL 数据库主从复制的具体实现步骤,如下图所示:

(1)当MySQL数据库执行写操作(insert、update、delete、replace)时,主库会将该变更记录到Binlog文件中。

(2)从库的IO Thread向主库发起Dump Binlog文件请求,主库的Log Dump线程将Binlog文件发送到从库。当主从库数据一致之后,主库再执行写操作会主动实时同步Binlog给从库。

(3)从库的IO Thread将接收到Binlog文件写入到本地的Relay Log(中继日志)中。

(4)从库的SQL线程读取 Relay Log(中继日志) 中的内容,将其解析成对应的SQL语句并执行,从而保证MySQL主从库数据的最终一致性。

从上述步骤来看,MySQL的主从复制应该属于推拉结合的模式,从库的IO Thread率先向主库发起Dump Binlog文件请求为拉模式,而后续的主库实时同步变更的Binlog为推模式。

当然,这里所说的是主从复制默认的异步模式,除此之外还有全同步模式和半同步模式,我们在下文中继续介绍。

主从复制模式

1、异步模式

如上文所述,MySQL主库执行写操作,并将该变更记录到Binlog文件中,此时就会给客户端返回结果,不需要等待从库对主库的日志事件进行确认。

这种模式的优点在于,性能高但数据一致性低,当主库尚未将数据同步到从库时发生宕机,会有数据丢失风险。

2、全同步模式

MySQL主库执行写操作,并将该变更记录到Binlog文件中,还需要等待所有从库对主库的日志事件进行确认后,才会给客户端返回结果。

这种模式很少在生产环境使用,虽然可以保证主从数据库的数据一致性,但性能却是最低的。

3、半同步模式

MySQL主库执行写操作,并将该变更记录到Binlog文件中,还需要等待至少一个从库(可配置)对主库的日志事件进行确认后,才会给客户端返回结果。

这种模式是在性能和数据一致性上进行了平衡。

这里需要说明的是,只有在从库的IO Thread将从主库接收的Binlog文件写入到本地的Relay Log中,才会对主库的日志事件进行确认。

Binlog日志格式

1、STATEMENT 格式

Binlog日志记录执行的SQL语句,这种方式性能最高且日志量少,缺点是使用NOW()、UUID()RAND()之类的函数会导致主从不一致。

举个例子,执行的SQL语句为:

UPDATE jd_user SET age = age + 1 WHERE id = 3;

那么所记录的Binlog日志也是一样的。

2、ROW 格式(默认)

Binlog日志记录每行数据变更前后的完整快照,其优点在于主从数据库的数据是强一致的,安全可靠,缺点是性能较低,在执行批量操作时会导致日志爆炸。举个例子,执行的SQL语句为:

UPDATE jd_user SET age = age + 1 WHERE id = 3;

那么所记录的Binlog日志也是一样的。

Table: tony.jd_user
Before: {id=3, age = 21}
After:  {id=3, age = 22}

当然,如果执行的SQL语句为

UPDATE jd_user SET age = age + 1;

如果表中的数据记录有几亿行的话,那就会产生几亿个类似的Binlog记录。

3、MIXED 模式

Binlog日志记录默认使用STATEMENT模式,当SQL语句中存在NOW()、UUID()RAND()之类的函数,可能引发主从不一致时,会自动切换为ROW模式。

主从同步延迟

如上文所述,以异步方式进行主从复制,会带来主从同步延迟的问题,可能会导致的现象如下:

当然,既然我们选择了在从库读取数据,那必须要接受并容忍些许的数据延迟,但延迟时间太久就无法容忍了。

接下来我们分析一下,都有哪些动作会导致长时间的主从同步延迟,以及如何进行规避。

1、大事务

如果我们在Binlog日志格式中选择了Row模式,或者在MIXED模式中由于一些情况走了Row模式,就会由于大批量的写操作导致Binlog日志同步延迟。

如上文所述,如果执行的SQL语句为:

UPDATE jd_user SET age = age + 1;

如果表中的数据记录有几亿行的话,那就会产生几亿个Binlog记录并同步到从库中,必然会产生主从延迟。这种情况的解决方案是,我们可以将一次大事务拆分为若干个小事务进行处理,并在执行过程中sleep一会儿。执行SQL语句如下:

UPDATE jd_user SET age = age + 1 WHERE id > 上一次更新的最大ID;

2、主库压力大

我经历过一个真实的业务场景,某在线教育平台周一中午12点开始约下周外教老师的课程,于是在12点到12点5分这个时间段约课的TPS会达到上万。

此时就会产生非常严重的主从延迟问题,大概15分钟后主库的数据才能完全同步到从库中。

这种情况可以通过主库分库的方式来分摊压力,让主库更快地记录Binlog并同步到从库中,来降低流量高峰期的主从延迟。当然,也可以考虑主库升配。

3、从库压力大

如果从库需要处理大量读请求,或是特别消耗资源的统计分析类场景,也会导致从库接收Binlog文件并写入到Relay Log,以及进行Relay Log重放出现延迟。

这种情况可以通过多搭建几台从库,优化SQL语句,或是引入Redis、ES、ClickHouse、Doris数据库的方式为从库降低压力。当然,也可以考虑从库升配。

4、 网络带宽和延迟

这种情况无法从代码层面解决,需要优化网络架构,确保主从服务器位于同一数据中心或高速网络环境中,减少网络延迟,也可以增加主从服务器之间的网络带宽,避免传输瓶颈。