MySQL 主从复制与读写分离实战

从库启动后,指向主库并开始复制。使用 GTID 时无需关心具体位点:

-- 从库执行
CHANGE MASTER TO
  MASTER_HOST='10.0.0.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='StrongReplPass!2026',
  MASTER_AUTO_POSITION=1;     -- 基于 GTID 自动定位

START SLAVE;

3.3 验证复制状态

-- 从库执行
SHOW SLAVE STATUS\G
-- 关键字段需为 Yes:
--   Slave_IO_Running: Yes
--   Slave_SQL_Running: Yes
--   Seconds_Behind_Master: 0   (延迟秒数,理想为 0)

-- 主库写入一条测试数据
INSERT INTO test.repl_check(ts) VALUES(NOW());
-- 从库查询应能看到
SELECT * FROM test.repl_check;

四、读写分离:在应用层落地

复制搭好后,下一步是让读请求走从库、写请求走主库。常见落地方式有两种。

4.1 中间件方案

ProxySQL / ShardingSphere-Proxy 在数据库前面做 SQL 解析与路由,对应用透明,支持权重、健康检查、故障自动摘除。适合多语言、多服务共用一套数据库的场景,运维成本略高。

4.2 代码层路由(Spring 多数据源)

轻量做法是应用内配置主从两个数据源,根据方法注解或事务类型路由。下面是基于 Spring 的简化示例:

// 用注解标记读操作,AOP 切到从库数据源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}

// 事务切面:有写事务走主库,@ReadOnly 走从库
@Around("@annotation(ReadOnly) || @annotation(Transactional)")
public Object route(ProceedingJoinPoint pjp) throws Throwable {
    boolean readOnly = methodHasAnnotation(pjp, ReadOnly.class)
        || isReadOnlyTransaction(pjp);
    DataSourceContext.set(readOnly ? SLAVE : MASTER);
    try { return pjp.proceed(); }
    finally { DataSourceContext.clear(); }
}

// 使用
@ReadOnly
public ProductDetail getProduct(Long id) {
    return productMapper.selectById(id); // 自动路由到从库
}

注意:在一个写事务内紧接着读刚写入的数据,必须从主库读,否则会因复制延迟读到旧值。简单稳妥的策略是”事务内一律走主库“。

五、生产避坑:延迟、脑裂与监控

主从架构上线后,真正的挑战在运维。下表汇总最常见的问题与对策:

现象根因对策
Seconds_Behind_Master 持续升高从库慢 SQL、单线程回放瓶颈、大事务优化从库查询;8.0 开启多线程复制(MTS);拆分大事务
主从数据不一致从库被误写、复制报错跳过从库设 super_read_only;用 pt-table-checksum 定期校验
主库宕机后脑裂多从提升、VIP 漂移冲突用 Orchestrator/MHA 做自动故障切换,加防双写 fencing
复制中断(SQL 线程报错)从库缺失对象、数据冲突定位 GTID 缺口;必要时跳过或重做从库

性能问题排查时,MySQL EXPLAIN 实战:读懂执行计划优化慢查询 能帮你定位从库上的慢回放语句;而复制链路上的死锁则可在 MySQL 死锁排查与预防实战 中找到系统化解法。

六、与连接池、备份的协同

读写分离后,主从各自都需要独立的连接池配置。从库通常承载更多读连接,可把最大连接数调高;主库则侧重控制写并发。连接池参数(如 HikariCP 的 maximumPoolSize、connectionTimeout)若设置不当,会引发连接耗尽雪崩,相关内容见 PgBouncer 实战:PostgreSQL 连接池调优,思路与 MySQL 通用。

最后提醒:主从复制不能替代备份。从库会同步你的误删操作,因此必须保留独立的时间点备份(全量 + binlog)。把备份放在从库执行,既不影响主库性能,又能随时基于 binlog 做定点恢复。

七、小结

主从复制与读写分离是 MySQL 扩展读能力、提升可用性的标准解法:以 binlog 为桥梁,用 GTID 简化位点管理,在中间件或代码层完成读写路由,再用监控与校验兜住延迟与一致性风险。搭好这套底座,你的数据库就能从容应对读流量的增长与突发故障。

重启主库后,创建一个专用于复制的账号,并授予 REPLICATION 权限:

-- 主库执行
CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'StrongReplPass!2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.0.%';
FLUSH PRIVILEGES;

-- 记录当前 binlog 位点(使用 GTID 可跳过此步)
SHOW MASTER STATUS;
-- 输出示例: File=mysql-bin.000003  Position=157

3.2 从库配置与挂载

# 从库 my.cnf
server-id = 2                 # 必须唯一,不能和主库或其他从库重复
relay_log = mysql-relay-bin
read_only = ON                # 从库只读,防止误写
super_read_only = ON          # 连超级用户也禁止写(8.0+)
log_slave_updates = ON        # 级联复制时需要

从库启动后,指向主库并开始复制。使用 GTID 时无需关心具体位点:

-- 从库执行
CHANGE MASTER TO
  MASTER_HOST='10.0.0.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='StrongReplPass!2026',
  MASTER_AUTO_POSITION=1;     -- 基于 GTID 自动定位

START SLAVE;

3.3 验证复制状态

-- 从库执行
SHOW SLAVE STATUS\G
-- 关键字段需为 Yes:
--   Slave_IO_Running: Yes
--   Slave_SQL_Running: Yes
--   Seconds_Behind_Master: 0   (延迟秒数,理想为 0)

-- 主库写入一条测试数据
INSERT INTO test.repl_check(ts) VALUES(NOW());
-- 从库查询应能看到
SELECT * FROM test.repl_check;

四、读写分离:在应用层落地

复制搭好后,下一步是让读请求走从库、写请求走主库。常见落地方式有两种。

4.1 中间件方案

ProxySQL / ShardingSphere-Proxy 在数据库前面做 SQL 解析与路由,对应用透明,支持权重、健康检查、故障自动摘除。适合多语言、多服务共用一套数据库的场景,运维成本略高。

4.2 代码层路由(Spring 多数据源)

轻量做法是应用内配置主从两个数据源,根据方法注解或事务类型路由。下面是基于 Spring 的简化示例:

// 用注解标记读操作,AOP 切到从库数据源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}

// 事务切面:有写事务走主库,@ReadOnly 走从库
@Around("@annotation(ReadOnly) || @annotation(Transactional)")
public Object route(ProceedingJoinPoint pjp) throws Throwable {
    boolean readOnly = methodHasAnnotation(pjp, ReadOnly.class)
        || isReadOnlyTransaction(pjp);
    DataSourceContext.set(readOnly ? SLAVE : MASTER);
    try { return pjp.proceed(); }
    finally { DataSourceContext.clear(); }
}

// 使用
@ReadOnly
public ProductDetail getProduct(Long id) {
    return productMapper.selectById(id); // 自动路由到从库
}

注意:在一个写事务内紧接着读刚写入的数据,必须从主库读,否则会因复制延迟读到旧值。简单稳妥的策略是”事务内一律走主库“。

五、生产避坑:延迟、脑裂与监控

主从架构上线后,真正的挑战在运维。下表汇总最常见的问题与对策:

现象根因对策
Seconds_Behind_Master 持续升高从库慢 SQL、单线程回放瓶颈、大事务优化从库查询;8.0 开启多线程复制(MTS);拆分大事务
主从数据不一致从库被误写、复制报错跳过从库设 super_read_only;用 pt-table-checksum 定期校验
主库宕机后脑裂多从提升、VIP 漂移冲突用 Orchestrator/MHA 做自动故障切换,加防双写 fencing
复制中断(SQL 线程报错)从库缺失对象、数据冲突定位 GTID 缺口;必要时跳过或重做从库

性能问题排查时,MySQL EXPLAIN 实战:读懂执行计划优化慢查询 能帮你定位从库上的慢回放语句;而复制链路上的死锁则可在 MySQL 死锁排查与预防实战 中找到系统化解法。

六、与连接池、备份的协同

读写分离后,主从各自都需要独立的连接池配置。从库通常承载更多读连接,可把最大连接数调高;主库则侧重控制写并发。连接池参数(如 HikariCP 的 maximumPoolSize、connectionTimeout)若设置不当,会引发连接耗尽雪崩,相关内容见 PgBouncer 实战:PostgreSQL 连接池调优,思路与 MySQL 通用。

最后提醒:主从复制不能替代备份。从库会同步你的误删操作,因此必须保留独立的时间点备份(全量 + binlog)。把备份放在从库执行,既不影响主库性能,又能随时基于 binlog 做定点恢复。

七、小结

主从复制与读写分离是 MySQL 扩展读能力、提升可用性的标准解法:以 binlog 为桥梁,用 GTID 简化位点管理,在中间件或代码层完成读写路由,再用监控与校验兜住延迟与一致性风险。搭好这套底座,你的数据库就能从容应对读流量的增长与突发故障。

当单机 MySQL 扛不住读流量、或一次误删需要快速恢复时,MySQL 主从复制与读写分离几乎是每个后端系统的必经之路。本文用可落地的配置与代码,带你从零搭建一主一从复制,并在应用层实现读写分离,避开生产环境最常见的复制延迟与数据不一致坑。

一、为什么需要主从复制与读写分离

绝大多数业务都是”读多写少”:电商商品详情、内容 feed、报表查询,读请求往往占 80% 以上。如果所有请求都打在同一台主库上,CPU 和连接数很快成为瓶颈。主从复制(Replication)把主库的变更同步到一个或多个从库,再配合读写分离把读流量导到从库,就能线性扩展读能力,同时为主库腾出写资源。

除此之外,主从架构还有两个隐性价值:一是异地容灾,从库可以部署在不同可用区,主库宕机时快速提升从库为主;二是在线备份,备份操作放到从库执行,避免锁表影响线上。关于备份的更多细节,可以参考 MySQL 备份恢复实战:全量备份与 binlog 恢复。

二、主从复制原理:binlog 与三种模式

MySQL 主从复制的基石是 binlog(二进制日志)。主库把所有数据变更(DDL、DML)按顺序写入 binlog,从库的 I/O 线程拉取这些日志,写入本地的 relay log(中继日志),再由 SQL 线程回放,从而实现数据一致。

2.1 异步复制(默认)

主库提交事务后立即返回客户端,不等待从库确认。性能最好,但存在数据丢失窗口:主库宕机时尚未同步的少量事务会丢失。这是默认模式,适合对一致性要求不极端、追求吞吐的场景。

2.2 半同步复制

主库提交后,至少等待一个从库接收并写入 relay log 才返回。牺牲少量延迟,换来”已提交事务至少落在一台从库”的保证,是金融、订单类系统的推荐配置。开启方式是在主从两端安装 rpl_semi_sync_master / rpl_semi_sync_slave 插件。

2.3 延迟问题与一致性

无论哪种模式,从库都存在一定复制延迟(seconds_behind_master)。大事务、从库慢查询、网络抖动都会放大延迟。理解事务隔离与可见性边界,对设计读写分离路由至关重要,建议先读 事务隔离级别实战:脏读、不可重复读、幻读与 MVCC。

三、一步一步搭建主从复制

下面以一主一从(MySQL 8.0)为例,给出最小可用配置。假设主库 IP 为 10.0.0.10,从库为 10.0.0.11。

3.1 主库配置 my.cnf

# 主库 my.cnf
server-id = 1
log_bin = mysql-bin
binlog_format = ROW          # 行格式,最安全,便于从库回放
binlog_row_image = FULL
expire_logs_days = 7         # 自动清理旧 binlog,避免磁盘打满
gtid_mode = ON               # 建议开启 GTID,故障切换更省心
enforce_gtid_consistency = ON

重启主库后,创建一个专用于复制的账号,并授予 REPLICATION 权限:

-- 主库执行
CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'StrongReplPass!2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.0.%';
FLUSH PRIVILEGES;

-- 记录当前 binlog 位点(使用 GTID 可跳过此步)
SHOW MASTER STATUS;
-- 输出示例: File=mysql-bin.000003  Position=157

3.2 从库配置与挂载

# 从库 my.cnf
server-id = 2                 # 必须唯一,不能和主库或其他从库重复
relay_log = mysql-relay-bin
read_only = ON                # 从库只读,防止误写
super_read_only = ON          # 连超级用户也禁止写(8.0+)
log_slave_updates = ON        # 级联复制时需要

从库启动后,指向主库并开始复制。使用 GTID 时无需关心具体位点:

-- 从库执行
CHANGE MASTER TO
  MASTER_HOST='10.0.0.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='StrongReplPass!2026',
  MASTER_AUTO_POSITION=1;     -- 基于 GTID 自动定位

START SLAVE;

3.3 验证复制状态

-- 从库执行
SHOW SLAVE STATUS\G
-- 关键字段需为 Yes:
--   Slave_IO_Running: Yes
--   Slave_SQL_Running: Yes
--   Seconds_Behind_Master: 0   (延迟秒数,理想为 0)

-- 主库写入一条测试数据
INSERT INTO test.repl_check(ts) VALUES(NOW());
-- 从库查询应能看到
SELECT * FROM test.repl_check;

四、读写分离:在应用层落地

复制搭好后,下一步是让读请求走从库、写请求走主库。常见落地方式有两种。

4.1 中间件方案

ProxySQL / ShardingSphere-Proxy 在数据库前面做 SQL 解析与路由,对应用透明,支持权重、健康检查、故障自动摘除。适合多语言、多服务共用一套数据库的场景,运维成本略高。

4.2 代码层路由(Spring 多数据源)

轻量做法是应用内配置主从两个数据源,根据方法注解或事务类型路由。下面是基于 Spring 的简化示例:

// 用注解标记读操作,AOP 切到从库数据源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}

// 事务切面:有写事务走主库,@ReadOnly 走从库
@Around("@annotation(ReadOnly) || @annotation(Transactional)")
public Object route(ProceedingJoinPoint pjp) throws Throwable {
    boolean readOnly = methodHasAnnotation(pjp, ReadOnly.class)
        || isReadOnlyTransaction(pjp);
    DataSourceContext.set(readOnly ? SLAVE : MASTER);
    try { return pjp.proceed(); }
    finally { DataSourceContext.clear(); }
}

// 使用
@ReadOnly
public ProductDetail getProduct(Long id) {
    return productMapper.selectById(id); // 自动路由到从库
}

注意:在一个写事务内紧接着读刚写入的数据,必须从主库读,否则会因复制延迟读到旧值。简单稳妥的策略是”事务内一律走主库“。

五、生产避坑:延迟、脑裂与监控

主从架构上线后,真正的挑战在运维。下表汇总最常见的问题与对策:

现象根因对策
Seconds_Behind_Master 持续升高从库慢 SQL、单线程回放瓶颈、大事务优化从库查询;8.0 开启多线程复制(MTS);拆分大事务
主从数据不一致从库被误写、复制报错跳过从库设 super_read_only;用 pt-table-checksum 定期校验
主库宕机后脑裂多从提升、VIP 漂移冲突用 Orchestrator/MHA 做自动故障切换,加防双写 fencing
复制中断(SQL 线程报错)从库缺失对象、数据冲突定位 GTID 缺口;必要时跳过或重做从库

性能问题排查时,MySQL EXPLAIN 实战:读懂执行计划优化慢查询 能帮你定位从库上的慢回放语句;而复制链路上的死锁则可在 MySQL 死锁排查与预防实战 中找到系统化解法。

六、与连接池、备份的协同

读写分离后,主从各自都需要独立的连接池配置。从库通常承载更多读连接,可把最大连接数调高;主库则侧重控制写并发。连接池参数(如 HikariCP 的 maximumPoolSize、connectionTimeout)若设置不当,会引发连接耗尽雪崩,相关内容见 PgBouncer 实战:PostgreSQL 连接池调优,思路与 MySQL 通用。

最后提醒:主从复制不能替代备份。从库会同步你的误删操作,因此必须保留独立的时间点备份(全量 + binlog)。把备份放在从库执行,既不影响主库性能,又能随时基于 binlog 做定点恢复。

七、小结

主从复制与读写分离是 MySQL 扩展读能力、提升可用性的标准解法:以 binlog 为桥梁,用 GTID 简化位点管理,在中间件或代码层完成读写路由,再用监控与校验兜住延迟与一致性风险。搭好这套底座,你的数据库就能从容应对读流量的增长与突发故障。

上一篇 MyBatis-Plus 实战:单表 CRUD 与分页插件零样板落地
下一篇 Spring AI 实战:Spring Boot 接入大模型与流式对话
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]