专栏文章
专栏文章
MySQL 系列
1. MySQL 系列 #01:MySQL 简介 2. MySQL 系列 #02:MySQL 数据类型与 Java 类型映射 3. MySQL 系列 #03:MySQL 基础架构与执行流程 4. MySQL 系列 #04:MySQL InnoDB 日志系统 5. MySQL 系列 #05:MySQL 事务与 MVCC 6. MySQL 系列 #06:MySQL 索引原理与优化 7. MySQL 系列 #07:MySQL 锁机制 8. MySQL 系列 #08:MySQL 性能问题排查 9. MySQL 系列 #09:MySQL 主备复制与高可用 10. MySQL 系列 #10:MySQL 实战技巧与常见陷阱 11. MySQL 系列 #11:MySQL 数据库设计规范 12. MySQL 系列 #12:MySQL SQL 函数与查询技巧 13. MySQL 系列 #13:MySQL InnoDB Buffer Pool 原理 14. MySQL 系列 #14:MySQL 排序与聚合原理

MySQL 系列 #09:MySQL 主备复制与高可用

发布于 2026-05-26 10:33 👁 9 次阅读
#mysql#binlog#replication#high-availability#gtid

梳理 MySQL 主备复制的工作原理、备库延迟的根因与解决方案、主库故障切换的流程,以及读写分离的正确姿势。


目录

章节 说明
主备复制原理 binlog 同步流程,三种 binlog 格式的影响
双主结构与循环复制 互为主备的配置与防循环机制
备库延迟的原因 单线程 vs 并行复制
主库故障切换 基于位点 vs 基于 GTID 的切换
读写分离的坑 过期读问题及几种解决方案
如何判断主库是否正常 select 1 的缺陷与正确的健康检查方式

主备复制原理

mysql replication

同步流程

sequenceDiagram
    participant M as 主库 (Master)
    participant B as 备库 (Slave)

    B->>M: 建立长连接(I/O Thread)
    M-->>B: 发送 binlog(binlog dump)
    Note over B: I/O Thread 写入 relay log
    B->>B: SQL Thread 读取 relay log,重放执行

三种 binlog 格式对复制的影响

格式 主备一致性 日志大小
statement ❌ 某些函数(UUID、NOW)主从结果不同
row ✅ 记录每行实际变化,主从完全一致
mixed ✅(自动切换)

生产环境推荐 row 格式,主从数据一致性最好,且支持精确恢复。


双主结构与循环复制

互为主备(A ↔ B):两个节点互相是对方的备库,同时只有一个节点对外提供写服务。

防止循环复制

binlog 中的每个事务都带有 server_id。备库重放 binlog 时,会检查 server_id:

A 写入事件(server_id=1)→ 同步到 B → B 重放
B 同步回 A 时:A 发现 server_id=1 是自己 → 丢弃,不再重放

配置要求:主备节点的 server_id 必须不同。


备库延迟的原因

主要原因

原因 说明
备库机器性能差 备库通常用低配机器,IO 和 CPU 处理能力弱
备库承担读压力 读查询消耗资源,影响 SQL Thread 的回放速度
大事务 主库执行了一个 10 分钟的大事务,备库同样需要 10 分钟
单线程回放 MySQL 5.5 及以前,SQL Thread 单线程,主库并发写入备库无法并行回放

并行复制(MySQL 5.6+)

-- 5.6:基于 database 的并行(不同 DB 的事务并行)
-- 5.7:基于组提交的并行(同一组提交的事务并行,效果更好)
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 4;  -- 并行线程数

大事务导致延迟的根治方案

拆分大事务:不要一次性删除/更新大量行,改为分批处理。


主库故障切换

mysql ha arch

基于 binlog 位点的切换(传统方式)

-- 将从库 B 切换为新主库 A' 的从库
CHANGE MASTER TO
  MASTER_HOST='A_prime_host',
  MASTER_PORT=3306,
  MASTER_USER='repl',
  MASTER_PASSWORD='xxx',
  MASTER_LOG_FILE='binlog.000003',  -- 需要人工确定同步位点
  MASTER_LOG_POS=123;

问题:位点难以精确定位,可能导致同步数据重复(幂等操作)或遗漏,需要依赖 sql_slave_skip_counter 跳过错误。

基于 GTID 的切换(推荐,MySQL 5.6+)

GTID = Global Transaction ID,格式:server_uuid:transaction_id

-- 开启 GTID
SET GLOBAL gtid_mode = ON;
SET GLOBAL enforce_gtid_consistency = ON;

-- 切换时不需要指定位点,MySQL 自动计算
CHANGE MASTER TO
  MASTER_HOST='A_prime_host',
  MASTER_PORT=3306,
  MASTER_USER='repl',
  MASTER_PASSWORD='xxx',
  MASTER_AUTO_POSITION=1;  -- 自动基于 GTID 同步

GTID 切换优势


读写分离的坑

过期读问题

读写分离后,写请求走主库,读请求走从库。从库有延迟时,读到的可能是旧数据(过期读)。

几种解决方案

方案 原理 适用场景
强制走主库 写完之后的读请求强制发往主库 对延迟敏感的关键操作
sleep 等待 写完后 sleep 1 秒再读从库 过于粗糙,不推荐
判断主从延迟 SHOW SLAVE STATUSSeconds_Behind_Master,延迟 > 阈值则读主库 中等场景
等主库位点 写完获取 binlog 位点,读从库时等该位点已同步再查询 较精确,有少量延迟
等 GTID 写完获取事务 GTID,读从库时 SELECT WAIT_FOR_EXECUTED_GTID_SET(gtid, timeout) ✅ 精确,推荐
-- 方案:等 GTID 同步后再读从库
-- 主库写完后获取 GTID
-- SELECT @@GLOBAL.gtid_executed;  → 'uuid:1-100'

-- 从库等待该 GTID 集合同步完成(超时返回 1,未超时返回 0)
SELECT WAIT_FOR_EXECUTED_GTID_SET('uuid:1-100', 1);

如何判断主库是否正常

select 1 的缺陷

-- 这个命令不访问任何表,只检查连接是否存活
-- 无法检测到:并发线程过多导致的 innodb_thread_concurrency 打满
select 1;

更可靠的健康检查

-- 方案1:查一个业务表(检测真实读写能力)
SELECT * FROM health_check;

-- 但如果 binlog 空间满了导致所有更新卡住,读操作仍然正常 → 假健康

-- 方案2:定期更新 health_check 表(同时检测写入能力)
UPDATE health_check SET t_modified = NOW() WHERE id = @@server_id;

-- 方案3:使用 performance_schema 检测(更全面)
SELECT * FROM performance_schema.replication_connection_status;

innodb_thread_concurrency 的陷阱

-- 当并发线程数超过 innodb_thread_concurrency(默认 0=不限制)时
-- 新的查询会等待,select 1 仍然能成功
-- 但 "SELECT * FROM t" 会超时或阻塞
-- 建议设置合理值(如 64),超过时报 ERROR 1205 而不是无响应
SET GLOBAL innodb_thread_concurrency = 64;

参考资料

  • 《MySQL 实战 45 讲》— 第 23 讲:MySQL 是怎么保证数据不丢的?
  • 《MySQL 实战 45 讲》— 第 24 讲:MySQL 是怎么保证主备一致的?
  • 《MySQL 实战 45 讲》— 第 25 讲:MySQL 是怎么保证高可用的?
  • 《MySQL 实战 45 讲》— 第 26 讲:备库为什么会延迟好几个小时?
  • 《MySQL 实战 45 讲》— 第 27 讲:主库出问题了,从库怎么办?
  • 《MySQL 实战 45 讲》— 第 28 讲:读写分离有哪些坑?
  • MySQL - Replication
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论