
第2章 复制的基本原理
第1章简单介绍了MySQL复制(技术)相关的基本概念,本章将详细介绍其基本原理。MySQL的复制技术自诞生以来,随着各种各样的应用场景对数据安全性及复制性能的要求不断提高,也在不断迭代与优化。要深刻理解MySQL的复制技术,就要从它的基本原理说起[1],下面将对此展开介绍。
2.1 概述
复制是基于主库(master)二进制日志中写入的关于该数据库的所有更改(更新、删除等)的日志记录来实现的。从库(slave)利用这些二进制日志中的事件记录进行回放来同步数据。但对于不修改任何数据的查询语句,主库不会记录二进制日志(例如SELECT语句),所以,对于查询语句,从库自然也没有需要回放的内容。
连接到主库的每个从库都会复制一份主库二进制日志的副本。从库利用来自主库的二进制日志进行回放,就可以模拟主库中的数据变更操作,例如,建表、修改表结构等DDL(Data Definition Language,数据定义语言)操作以及DML(Data Manipulation Language,数据操纵语言)操作。
提示:对于主库中的二进制日志同步到从库时发起请求的方向,在此有必要做一些说明。二进制日志的同步,既有从库主动请求的情况,也有主库主动推送的情况。
• 对于复制线程在主从之间新建立连接或重新建立连接的情况,不是主库主动推送二进制日志(因为这时主库并不知道需要发送哪些二进制日志给新建立连接的从库),而是从库主动向主库请求所需的二进制日志(从库向主库注册连接时,携带了从库自身所需二进制日志的位置信息)。主从之间的复制连接始终是从库先发起请求的,就算主库主动断开了从库的连接,重新建立连接时也是从库重试的,而不是主库。
• 如果复制线程已经在主从之间建立连接,而且从库已经完全接收建立连接时请求的二进制日志内容,后续的增量二进制日志是由主库主动推送给从库的,而不是从库主动向主库请求的(因为这时主库随时都可能写入新的内容,从库难以即时感知,也没必要即时感知)。例如,主库在某段时间内没有写入任何数据,从库已经完全接收了最新的二进制日志内容,当主库再次写入新的数据时产生的新二进制日志,主库会直接发送给从库,而不需要发送一个通知给从库I/O线程,然后让从库来拉取(主库在产生新的二进制日志时,只会通知主库自身的Binlog Dump线程,而不会通知从库I/O线程,当主库的Binlog Dump线程收到通知之后,会直接把新产生的二进制日志发送给从库I/O线程)。
每个从库的复制操作都是独立运行的,即每个从库对从主库拉取的二进制日志的回放都是独立发生的,互不影响。每个从库都按照自己的进度读取主库二进制日志(从主库中同步的二进制日志保存在每个从库自身的中继日志中)并进行回放来更新自身的数据,而且每个从库都可以独立地随意启动/停止自己的I/O线程和SQL线程。
主库和从库都会定期输出在复制过程中的状态,可以通过相关的复制状态变量来监控这些状态的变化。从库在进行回放之前,会先将从主库接收的二进制日志写入本地的中继日志,中继日志中还记录了主库二进制日志文件位置的有关信息。
2.2 细节
MySQL的复制功能用三个线程来实现:一个线程在主库上(Binlog Dump线程),两个线程在从库上(I/O线程和SQL线程),如图2-1所示。

图2-1
结合图2-1从左往右看,复制的原理大致如下:
(1)用户提交对数据的修改,然后Master(主库)把所有数据库变更写进Binary Log(二进制日志),主库通过Binlog Dump 线程把二进制日志内容推送给Slave(从库),从库被动接收数据,不是主动去获取,除非是新建连接。
• 当从库正常连接到主库时,在主库中使用SHOW PROCESSLIST语句可以查看到标识为“Binlog Dump”的线程。
• Binlog Dump线程在读取二进制日志中要发送到从库的每个事件时,会获取二进制日志上的锁。一旦读取完事件,即使事件还未发送到从库,二进制日志上的锁也会被释放。
(2)在从库上执行START SLAVE语句时,已经使用CHANGE MASTER TO语句配置好复制信息,从库会创建一个I/O线程,该线程连接到主库并请求主库为其发送所需的二进制日志(从库向主库注册连接时,里面携带了请求的二进制日志的位置,该位置表示从库所请求的二进制日志的起点)。
• 从库I/O线程与主库的Binlog Dump线程成功建立连接之后,从库I/O线程接收主库Binlog Dump线程发送的二进制日志,并将它们写入从库本地的Relay Log(中继日志文件)。
• I/O线程的状态可以通过SHOW SLAVE STATUS语句输出的Slave_IO_running字段值来查看,或者通过SHOW STATUS语句输出的Slave_running状态变量查看(该状态变量在MySQL 5.7中已弃用,MySQL 8.0已将其移除)。
(3)从库SQL线程读取并解析中继日志中的内容,按照读取的顺序进行回放(二进制日志中存放的事务顺序就是主库中事务的提交顺序),并将数据变更写入本地数据库文件中,这样就实现了数据在主从数据库(实例)之间的同步。
每一对主从关系中,都有三个线程。主库可以连接多个从库,而且会为每一个连接成功的从库创建一个Binlog Dump线程;从库也可以连接多个主库(多源复制),而且会为每一个连接成功的主库创建自己的I/O线程和SQL线程。
从库使用两个线程分别将主库的二进制日志读取到本地,并应用这些日志以实现主从之间的数据同步。因此,即便SQL线程执行语句缓慢,也不会影响I/O线程读取日志的速度(正常情况下,I/O线程不会成为性能瓶颈,除非网络出现问题)。如果SQL线程已经应用完所有的中继日志,就表明I/O线程已经获取了主库中所有的二进制日志。
SHOW PROCESSLIST语句提供的信息可以告诉你主库和从库上有关复制的一些状态,如下所示:

关于查看复制状态的更完整的介绍,见第9章“通过PERFORMANCE_SCHEMA库检查复制信息”和第10章“通过其他方式检查复制信息”。
[1]注:从MySQL 5.7开始,支持能够兼顾数据一致性和数据库高可用性的组复制拓扑,由于本书只围绕主从复制拓扑展开,所以后续提及的“复制技术”仅针对“主从复制拓扑”。