RSS
菜单
全部文章快讯开发科技深度热点

第 22 章 主从复制与高可用

内容摘要

主从复制(Replication)是 MySQL 高可用架构的基石——它让一台主库的数据实时同步到多台从库,从而分担读压力、提供故障切换能力。本篇系统讲解 binlog 三种格式、主从搭建流程、GTID 复制、半同步复制、主从延迟监控与故障切换方案。学完本篇,你将能独立搭建一套一主两从的复制环境,并能在主库宕机时把业务切到从库。本系列示例基于 MySQL 8.0 语法。

📖 本篇简介:主从复制(Replication)是 MySQL 高可用架构的基石——它让一台主库的数据实时同步到多台从库,从而分担读压力、提供故障切换能力。本篇系统讲解 binlog 三种格式、主从搭建流程、GTID 复制、半同步复制、主从延迟监控与故障切换方案。学完本篇,你将能独立搭建一套一主两从的复制环境,并能在主库宕机时把业务切到从库。本系列示例基于 MySQL 8.0 语法。

目录

  • 1 主从复制原理
  • 2 Binlog 三种格式
  • 3 主从搭建实操
  • 4 GTID 复制
  • 5 半同步复制
  • 6 主从延迟监控与处理
  • 7 故障切换方案
  • 8 主从复制速查表
  • 9 常见坑与注意事项
  • 10 练习题与参考答案

1 主从复制原理

主从复制的核心思路:主库把所有写操作记录到 binlog(二进制日志),从库拉取主库的 binlog 并在本地重放,从而保持与主库数据一致。整个过程由三个线程协作完成:

| 线程 | 所在节点 | 职责 |

|---|---|---|

| Binlog Dump Thread | 主库 | 把 binlog 发给从库 |

| I/O Thread | 从库 | 接收主库发来的 binlog,写到本地 relay log(中继日志) |

| SQL Thread | 从库 | 读取 relay log,重放 SQL,更新本地数据 |

复制流程可以简化成:

主库 ──写 binlog──> Binlog Dump
                         │
                         ▼ 网络传输
从库 I/O Thread ──写 relay log──> SQL Thread ──重放──> 从库数据

MySQL 8.0 中 SQL Thread 已经从单线程升级为并行复制(基于组提交),默认可以并行重放多个数据库的事务,从库追平速度大幅提升。

1.1 异步复制 vs 同步复制

默认的主从复制是异步复制(Async Replication):主库写完 binlog 就返回成功,不等从库接收。优点是性能好,缺点是主库宕机时,可能有一部分 binlog 还没传到从库,数据就丢了。

如果业务不能接受丢数据,就要用半同步复制(Semi-sync)——主库至少等一台从库确认收到 binlog 才返回成功。这是本章后面要讲的重点。

1.2 并行复制

MySQL 5.7 之前从库 SQL 线程是单线程的,主库每秒写 1000 个事务,从库一秒只能重放 100 个,延迟越拉越大。MySQL 8.0 的并行复制(基于 WRITESET)让"不修改同一行的事务"可以在从库并行重放,并发能力接近主库。

-- 查看并行复制模式
SHOW VARIABLES LIKE 'binlog_transaction_dependency_tracking';
-- 默认 COMMIT_ORDER;WRITESET 模式并行度更高,但要求主库开 binlog 行镜像

生产环境开 WRITESET 的前提:主库 binlog 必须是 ROW 格式(本篇推荐的格式),并且 binlog_row_image=FULL。

2 Binlog 三种格式

binlog 是主从复制的源头。它有三种记录格式,直接影响从库重放的正确性和体积:

| 格式 | 记录内容 | 优点 | 缺点 |

|---|---|---|---|

| STATEMENT(SBR) | 原始 SQL 语句 | 日志小、可读性好 | 函数(NOW()、UUID())在从库执行结果可能不同 |

| ROW(RBR) | 每一行的前后镜像 | 绝对正确 | 日志体积大(一次 UPDATE 百万行就记百万行) |

| MIXED | 默认 STATEMENT,遇到不确定函数自动切 ROW | 兼顾 | 调试稍麻烦 |

-- 查看当前 binlog 格式
SHOW VARIABLES LIKE 'binlog_format';

-- 设置为 ROW(生产强烈推荐)
SET GLOBAL binlog_format = 'ROW';

生产环境几乎都用 ROW。理由:STATEMENT 下 UPDATE ... WHERE create_time = NOW() 这种 SQL 在从库执行时 NOW() 已经不是同一时刻,会造成主从不一致。ROW 格式直接记"id=100 这行从 X 改成 Y",绝对正确。

2.1 查看 binlog

-- 查看所有 binlog 文件
SHOW BINARY LOGS;

-- 查看当前正在写的 binlog
SHOW MASTER STATUS;

-- 解析某个 binlog 的内容(系统命令 mysqlbinlog)
-- mysqlbinlog --start-datetime="2026-10-07 10:00:00" /var/lib/mysql/binlog.000001

SHOW MASTER STATUS 输出里的 File 和 Position 两个字段,是从库"从哪里开始拉 binlog"的坐标——搭建主从时必须告诉从库。

3 主从搭建实操

下面演示一台主库(192.168.1.10)搭一台从库(192.168.1.11)的完整流程。

3.1 主库配置

[mysqld]
# 主库唯一 server_id(从库不能重复)
server_id = 100
# 开启 binlog
log_bin = /var/lib/mysql/binlog
# binlog 格式用 ROW
binlog_format = ROW
# 只同步需要的库(可选)
binlog_do_db = shop
# binlog 过期天数(自动清理)
expire_logs_days = 7

配置完重启 MySQL,在主库上创建复制账号:

-- 创建从库复制专用账号,只允许从 192.168.1.11 连接
CREATE USER 'repl'@'192.168.1.11' IDENTIFIED BY 'Repl@2026';
-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.1.11';
FLUSH PRIVILEGES;

-- 锁表,确保数据一致性(只读锁,不阻塞读)
FLUSH TABLES WITH READ LOCK;

-- 记录当前 binlog 坐标(File + Position),搭建时要用
SHOW MASTER STATUS;
-- 假设输出:binlog.000003  1572

-- 此时用 mysqldump 备份主库数据到从库,备份完再 UNLOCK
-- mysqldump -uroot -p --single-transaction --master-data=2 shop > shop.sql

UNLOCK TABLES;

3.2 从库配置

[mysqld]
# 从库 server_id 必须与主库不同
server_id = 101
# 开启 relay log
relay_log = /var/lib/mysql/relaylog
# 从库默认只读(超级用户除外)
read_only = ON
# 防止从库被误写
super_read_only = ON

把主库导出的 shop.sql 导入从库,然后在从库上指定主库坐标:

-- 告诉从库:主库在哪里、用什么账号、从哪个 binlog 坐标开始拉
CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='192.168.1.10',
    SOURCE_USER='repl',
    SOURCE_PASSWORD='Repl@2026',
    SOURCE_LOG_FILE='binlog.000003',
    SOURCE_LOG_POS=1572;

-- MySQL 8.0 之前的旧语法是 MASTER_HOST/MASTER_LOG_FILE 等,仍兼容

-- 启动复制
START REPLICA;

-- 查看从库状态
SHOW REPLICA STATUS\G

SHOW REPLICA STATUS\G 输出里关键看两行:

  • Replica_IO_Running: Yes:I/O 线程正常拉 binlog;
  • Replica_SQL_Running: Yes:SQL 线程正常重放。

两个都是 Yes 才算搭好了。

4 GTID 复制

传统主从要手动记 binlog 文件和位置(File + Position),主库换了、binlog 轮转过,坐标就乱了。GTID(Global Transaction Identifier)是 MySQL 5.6 引入的全局事务编号,每个事务在主库执行时自动分配一个全局唯一 ID,从库自动识别"我已经执行到哪个 GTID",不用再手动记坐标。

4.1 开启 GTID

[mysqld]
# 主库和从库都要开
gtid_mode = ON
enforce_gtid_consistency = ON

开启后,从库搭建就简化成:

-- 从库:直接告诉它主库地址,自动找位置
CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='192.168.1.10',
    SOURCE_USER='repl',
    SOURCE_PASSWORD='Repl@2026',
    SOURCE_AUTO_POSITION=1;   -- 关键:自动定位 GTID

START REPLICA;

4.2 查看 GTID

-- 主库已经执行过的 GTID
SHOW MASTER STATUS;
-- Executed_Gtid_Set 字段显示已执行的所有 GTID

-- 从库已经接收到、重放过的 GTID
SHOW REPLICA STATUS\G
-- Retrieved_Gtid_Set / Executed_Gtid_Set

GTID 的好处:主库宕机、切到新主库时,从库只要 CHANGE SOURCE 指过去,自动知道"新主库上哪些事务我已经有了",跳过不重复执行。这是 MHA、Orchestrator 等高可用方案能自动切换的基础。

4.3 用 Python 模拟 GTID 集合对比(本机实跑)

GTID 本质上是一个全局唯一的事务编号,格式是 uuid:seq。从库判断"我有没有执行过某个 GTID",就是在自己的已执行集合里查一下。

# 模拟主库和从库的 GTID 集合
master_executed = {"host1:1", "host1:2", "host1:3", "host2:1", "host2:2"}
slave_executed  = {"host1:1", "host1:2", "host2:1"}

# 从库还需要从主库补哪些事务
missing = master_executed - slave_executed
print("从库还缺的 GTID =", sorted(missing))
print("从库已有的 GTID =", sorted(slave_executed))

实跑输出:

从库还缺的 GTID = ['host1:3', 'host2:2']
从库已有的 GTID = ['host1:1', 'host1:2', 'host2:1']

这个集合差就是从库启动后要从主库拉取并执行的事务。自动定位(SOURCE_AUTO_POSITION=1)的本质就是:从库把自己的 GTID 集合告诉主库,主库只发集合差集里的事务。

5 半同步复制

异步复制的风险是主库宕机时丢数据。半同步(Semi-sync)让主库至少等一台从库"收到 binlog 并写入 relay log"才返回成功,牺牲一点性能换数据安全。

5.1 安装半同步插件

-- 主库安装半同步模块(Master 端)
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;

-- 从库安装半同步模块(Replica 端)
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = 1;

-- 主库至少等几个从库确认
SET GLOBAL rpl_semi_sync_source_wait_for_replicas = 1;
-- 超时时间:等不到从库确认就降级为异步(默认 10s)
SET GLOBAL rpl_semi_sync_source_timeout = 10000;

5.2 半同步的降级机制

如果所有从库都挂了、主库等不到确认,超过 timeout 后,主库会自动降级为异步复制,继续服务,不会一直堵着。等从库恢复后,再自动切回半同步。这种"宁可慢、不可丢"的模式是金融、订单类业务的标配。

6 主从延迟监控与处理

主从复制最大的痛点是主从延迟(Replication Lag)——主库已经执行了 1000 条事务,从库才重放了 800 条。如果读流量走从库,用户就会看到"刚下单却查不到订单"。

6.1 查看延迟

-- 在从库上查看
SHOW REPLICA STATUS\G
-- 关键看:
-- Seconds_Behind_Master: 3   (落后主库多少秒)

Seconds_Behind_Master 的原理是:从库 SQL 线程正在重放的事务的"主库执行时间"和当前系统时间之差。这个值为 0 不代表完全一致,只能粗略参考。

更准确的方式是在主库写入一个时间戳表,从库查询这行的时间戳与当前时间差:

-- 主库上建心跳表
CREATE TABLE repl_heartbeat (ts DATETIME);

-- 主库定时(每秒)写一次
-- UPDATE repl_heartbeat SET ts = NOW();

-- 从库查询:当前时间 - 这行的 ts 就是真实延迟
SELECT TIMESTAMPDIFF(SECOND, ts, NOW()) AS lag FROM repl_heartbeat;

6.2 延迟常见原因

| 原因 | 表现 | 解决 |

|---|---|---|

| 从库单线程重放(老版本) | 大事务卡很久 | 升级 8.0 并行复制 |

| 从库性能差(CPU/IO 弱) | 延迟随写入波动 | 从库硬件升级 |

| 大事务(一次性 UPDATE 百万行) | 延迟突然飙升 | 拆小事务、分批提交 |

| 从库开了慢查询、备份 | 抢占 IO | 错峰、隔离 |

| 主库 binlog 格式是 ROW 大事务 | 从库重放慢 | 拆事务 |

6.3 减少延迟的手段

1. 主库写大事务拆小:一次提交 100 行,不要一次提交 100 万行;

2. 从库开并行复制(MySQL 8.0 默认基于 WRITESET 并行);

3. 从库硬件不弱于主库,最好是 SSD;

4. 关键读("刚下单查订单")强制走主库,不要为了分担压力把这种强一致读也走从库。

7 故障切换方案

主库宕机后,要把业务切到新主库,这个过程叫"故障切换"(Failover)。常见方案:

| 方案 | 原理 | 优点 | 缺点 |

|---|---|---|---|

| 手动切换 | DBA 手动 CHANGE MASTER | 简单 | 慢、依赖人 |

| MHA | 自动选最从从库提升为主 | 成熟 | 要装 Perl、维护复杂 |

| Orchestrator | Go 写的自动切换工具 | Web 界面、可视化 | 要单独部署 |

| 云厂商 RDS | 云自带高可用 | 零运维 | 锁定云 |

| MySQL InnoDB Cluster | 官方 MGR + Router | 官方推荐 | 学习成本高 |

7.1 切换的核心步骤

手动切换一次主库的步骤(以主库挂了、从库 192.168.1.11 提升为新主库为例):

1. 确认老主库真的挂了(避免脑裂);

2. 在从库上执行 STOP REPLICA;

3. 确认从库已经追完所有 relay log(Seconds_Behind_Master=0);

4. 在从库上 RESET MASTER 或清理旧复制配置;

5. 把应用连接指向新主库;

6. 原来的其他从库重新 CHANGE SOURCE TO 指向新主库。

-- 新主库(原来的从库):解除只读
SET GLOBAL read_only = OFF;
SET GLOBAL super_read_only = OFF;

-- 其他从库:改指向新主库
CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='192.168.1.11',
    SOURCE_USER='repl',
    SOURCE_PASSWORD='Repl@2026',
    SOURCE_AUTO_POSITION=1;
START REPLICA;

7.2 脑裂问题

最危险的情况:老主库其实没挂,只是网络断了。这时候你把业务切到新主库,老主库又"复活"继续接收写,两边都在写,数据就分叉了(脑裂)。

解决:

  • 切换前用第三方手段(比如 VIP 漂移脚本、云负载均衡)确认老主库真的不可达;
  • 老主库恢复后,先 RESET MASTER 再以从库身份加入新主库,不要直接对外服务。

8 主从复制速查表

| 命令/视图 | 作用 |

|---|---|

| SHOW MASTER STATUS | 主库当前 binlog 坐标 |

| SHOW REPLICA STATUS\G | 从库 IO/SQL 线程状态 |

| START REPLICA | 启动从库复制 |

| STOP REPLICA | 暂停从库复制 |

| SHOW BINARY LOGS | 列出主库所有 binlog |

| CHANGE REPLICATION SOURCE TO ... | 配置主库坐标 |

| Seconds_Behind_Master | 从库延迟秒数 |

| gtid_executed | 当前已执行的 GTID 集合 |

| mysqlbinlog 命令行 | 解析 binlog 文件 |

| rpl_semi_sync_source_enabled | 主库半同步开关 |

关键参数速记:

| 参数 | 作用 | 推荐值 |

|---|---|---|

| server_id | 实例唯一编号 | 主从不重复 |

| log_bin | 开启 binlog | ON |

| binlog_format | binlog 格式 | ROW |

| gtid_mode | 开启 GTID | ON |

| relay_log | 中继日志路径 | 独立磁盘 |

| read_only | 从库只读 | ON |

| expire_logs_days | binlog 保留天数 | 7 |

9 常见坑与注意事项

坑 1:主从 server_id 一样。

如果主从 server_id 配重复,从库会报错、拒绝复制。每个实例必须唯一,一般用 IP 末段做编号。

坑 2:从库开了 read_only 但超级用户还能写。

read_only=ON 只限制普通用户,root 超级用户依然能写。要同时开 super_read_only=ON,连 root 都不能写。

坑 3:binlog 格式用 STATEMENT。

线上跑了几年 STATEMENT,某天发现从库数据错了,追了半天是 NOW() / UUID() 函数导致的。生产一律 ROW。

坑 4:大事务导致主从延迟飙升。

一次 UPDATE orders SET ... WHERE create_time < '2025-01-01' 刷了 500 万行,主库一秒完成,从库重放要半小时。大事务必须拆小,比如按主键区间分批。

坑 5:以为从库绝对只读。

从库上不小心执行了一个写操作(比如手工修数据),这行就在主从之间分叉了,后续复制可能直接报错。从库写操作要通过主库代理。

坑 6:脑裂。

网络抖动时自动切换,老主库又活过来,两边都在写。一定要有"老主库复活后自动降级为从库"的机制,不能让它继续接流量。

坑 7:binlog 没设过期,磁盘被撑爆。

主库一直写 binlog 不清理,几个月后磁盘满、MySQL 挂掉。必须设 expire_logs_days 或 binlog_expire_logs_seconds。

10 练习题与参考答案

练习 1:主从复制的三个线程分别叫什么、在主还是从、做什么?

<details>

<summary>参考答案</summary>

Binlog Dump Thread(主库):把 binlog 发给从库;I/O Thread(从库):接收 binlog 写到 relay log;SQL Thread(从库,8.0 已并行化):读 relay log 重放 SQL。

</details>

练习 2:为什么生产环境 binlog 推荐用 ROW 格式?

<details>

<summary>参考答案</summary>

STATEMENT 格式记录原始 SQL,像 NOW()、UUID()、RAND() 这类函数在主从执行时结果不同,会导致主从不一致。ROW 格式记录每一行的前后镜像,从库直接按镜像改,绝对正确。代价是日志体积大,但磁盘现在便宜,正确性优先。

</details>

练习 3:从库 Seconds_Behind_Master 显示 100 秒,你会怎么排查和处理?

<details>

<summary>参考答案</summary>

1. 先看从库是否在重放大事务:SHOW REPLICA STATUS 看 Executed_Gtid_Set 是否在推进;

2. 查主库最近是否有大事务:SHOW BINLOG EVENTS;

3. 看从库 CPU/IO 是否打满;

4. 长期延迟要从库硬件升级、开并行复制、把大事务拆小。

</details>

练习 4:半同步复制相比异步复制,牺牲了什么、换来了什么?

<details>

<summary>参考答案</summary>

牺牲了主库的写入响应时间(要等从库确认收到 binlog);换来了"主库宕机时已提交事务不丢"的可靠性。当从库不可用时,半同步会超时降级为异步,避免主库被卡死。

</details>

练习 5:主库宕机要切到从库,怎么避免脑裂?

<details>

<summary>参考答案</summary>

切换前用第三方监控/HA 软件确认老主库真的不可达;老主库恢复后不能直接对外服务,要先清空本地 binlog、以从库身份加入新主库;应用层通过 VIP 或代理(如 ProxySQL)统一入口,切换时只改指向,不让应用直连老主库。

</details>

下一篇预告:第 23 章 分库分表与分区

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 22 章 主从复制与高可用》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。