MySQL 主从复制是构建高可用架构、读写分离和数据备份的基础。它的核心思想并不复杂:让一台服务器(从库)持续同步另一台服务器(主库)上发生的所有数据变更。理解其原理,能帮助你在发生主从延迟或中断时快速定位问题,而不会面对一片茫然。
19.1.1 整体复制流程
主从复制的运作可以浓缩为三个步骤,对应三个线程:
- 主库上,当有数据变更(如 INSERT、UPDATE、DELETE)时,这些变更会被记录到二进制日志(Binlog)中。
- 从库启动复制后,会有一个 IO 线程通过常规客户端连接协议,向主库请求 Binlog 事件。主库为每个从库连接启动一个 Binlog Dump 线程,负责读取本地的 Binlog 并发送给从库。
- 从库的 IO 线程收到这些事件后,先写入本地的中继日志(Relay Log)。然后从库的 SQL 线程读取 Relay Log 中的事件,在本地重放执行,最终实现与主库数据的一致。
这三个线程——主库的 Binlog Dump 线程、从库的 IO 线程、从库的 SQL 线程——构成了复制的核心骨架,缺一不可。
19.1.2 Binlog 与 Binlog Dump 线程
Binlog(二进制日志)是主从复制的唯二数据来源(另一个是 GTID,后面会介绍)。它记录了数据库上所有可能引起数据变更的 SQL 语句(基于语句的复制)或受影响行的实际数据变更(基于行的复制)。Binlog 是物理日志吗?不,它存储的是逻辑的更新事件,而不是物理页的变更,这使得 MySQL 可以在不同版本、甚至不同操作系统的服务器之间进行复制。
在复制过程中,主库并不主动“推”数据给从库,而是等待从库连接并请求。当从库的 IO 线程发出请求时,主库会为这个连接分配一个专门的 Binlog Dump 线程。这个线程会:
- 首先检查从库请求的 Binlog 文件名和位置(或 GTID 集合)。
- 然后顺序读取本地的 Binlog 文件,将符合条件的事件逐个发送给从库。读取是顺序 I/O,效率极高。
- 一旦当前的文件读完,如果还有后续事件写入,Dump 线程会等待并持续发送,保持一个“准实时”的推送流。
- 如果从库断连后重连,Dump 线程可以继续从上一次断开的位置(或 GTID)开始发送,不会重复或遗漏。
你可以通过 SHOW PROCESSLIST 在主库上看到正在为从库服务的 Dump 线程,它的 Command 通常显示为 Binlog Dump。这个线程是主库上唯一主动为复制服务的组件,它对主库的正常读写几乎没有影响。
19.1.3 从库的 IO 线程
从库上的 IO 线程负责连接主库,接收 Binlog 事件并写入 Relay Log。它的工作流程是:
- 根据
CHANGE MASTER TO指定的连接信息,使用一个普通的 MySQL 客户端连接主库。 - 发送一个“Binlog Dump”命令给主库,附带当前从库已经接收到的 Binlog 位置(如
mysql-bin.000001,位置 120)或 GTID 集合。 - 接收主库 Dump 线程不断推送过来的事件流。
- 将这些事件顺序写入本地的 Relay Log 文件。
IO 线程有几个关键行为需要注意:
- 它是独立工作的,不会因为 SQL 线程的执行快慢而阻塞,两者是异步的。因此,从库上 IO 线程和 SQL 线程可以有不同的进度。
- 如果网络中断或主库重启,IO 线程会检测到连接断开,并自动尝试重连(默认重连间隔由
MASTER_RETRY_COUNT和MASTER_CONNECT_RETRY控制)。 - IO 线程会定期更新本地的
master.info文件(或表mysql.slave_master_info),记录当前读取的 Binlog 文件名和位置,以便下次启动时能继续。
你可以用 SHOW SLAVE STATUS 查看 IO 线程的状态,其中 Slave_IO_Running 显示为 Yes 表示 IO 线程正常,Master_Log_File 和 Read_Master_Log_Pos 会告诉你目前从主库拉取到了哪个位置。
19.1.4 从库的 SQL 线程
SQL 线程是真正在从库上重放数据变更的线程。它从本地的 Relay Log 中读取事件,然后像普通客户端一样执行这些事件对应的 SQL 或行变更。
这个线程有几个值得关注的特性:
- 串行执行(默认):从库的 SQL 线程只有一个,它严格按照 Relay Log 中事件的顺序逐一执行。这意味着主库上即使并发执行的事务,到了从库也会被串行化重放。这也是传统复制容易产生延迟的根本原因——如果主库写并发很高,从库单线程回放可能跟不上。
- 可以开启并行复制:MySQL 5.6 开始引入了基于 schema 的并行复制,5.7 引入了基于组提交的并行复制(
LOGICAL_CLOCK),8.0 进一步优化。通过调整slave_parallel_workers参数,你可以让从库使用多个工作线程并行执行 Relay Log 中的事务,大幅降低延迟。这是解决主从延迟最直接有效的手段之一。 - 错误处理:SQL 线程执行某条语句出错时(比如从库上意外多删了一条数据导致复制失败),默认会停止运行,将
Slave_SQL_Running设为 No。你可以根据错误号选择跳过该错误继续(sql_slave_skip_counter),但必须谨慎,避免数据不一致。
通过 SHOW SLAVE STATUS,Slave_SQL_Running 显示 SQL 线程是否运行,Relay_Log_File 和 Exec_Master_Log_Pos 告诉你已经执行到主库的哪个 Binlog 位置。对比 IO 线程的读取位置,你可以计算出主从延迟到底卡在传输还是执行上。
19.1.5 一个具体的复制启动例子
假设你配置好一台从库,并执行:
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='repl',
MASTER_PASSWORD='pass',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
在这一刻:
- 从库的 IO 线程连接主库,发送请求:“请从 mysql-bin.000001 的 154 位置开始给我发事件”。
- 主库的 Dump 线程准备好最新的 Binlog 文件,从那个位置开始顺序发送 INSERT/UPDATE/DELETE 等事件。
- 从库 IO 线程收到事件后,写入 Relay Log,并记录当前已经收到 154+收到的长度(比如 520)。
- 从库 SQL 线程读 Relay Log,将收到的事件一条条在本地执行,同时更新已执行的位置。
- 当主库有新的数据变更时,Dump 线程会持续推送,形成实时的同步流。
如果中途从库网络中断,IO 线程会报错,SQL 线程消耗完 Relay Log 中已有的事件后暂停。网络恢复后,IO 线程重新连接,从故障前记录的位置继续拉取,不会丢失任何已确认接收的事件。
19.1.6 监控与实用命令
日常运维中,你需要依靠几个命令快速掌握主从状态:
SHOW MASTER STATUS:查看主库当前正在写入的 Binlog 文件和位置。SHOW SLAVE STATUS\G:查看从库 IO 和 SQL 线程状态、日志文件、位置、延迟秒数(Seconds_Behind_Master)、错误信息等。SHOW PROCESSLIST:可以看到主库上的 Binlog Dump 线程和从库上的 IO/SQL 线程。SELECT * FROM performance_schema.replication_connection_status等,用于更细致的复制监控。
掌握了这三个线程的分工协作,你就接触到了 MySQL 复制最底层的“骨架”。后续任何高可用、读写分离、数据一致性处理方案,都建立在这个简单而可靠的设计之上。