MySQL 主从复制与读写分离实战:从异步复制到高可用切换的完整方案 原创
引言
单机 MySQL 在数据量增长或可用性要求提高后会遇到两个瓶颈:磁盘容量上限和单点故障风险。主从复制(Replication)是解决这两个问题的基础方案,配合读写分离还能显著提升整体吞吐。本文完整走通异步复制搭建、复制拓扑、读写分离、监控排障到生产切换的全流程。
一、复制原理:理解 binlog 三种格式
MySQL 主从复制的核心是 binlog。主库将数据变更写入二进制日志,从库拉取并重放:
- 主库执行事务并写入 binlog
- 从库 IO 线程连接主库,请求 binlog,写入本地 relay log(中继日志)
- 从库 SQL 线程读取 relay log,按顺序重放事件
binlog 有三种格式,选型直接影响复制一致性和性能:
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | 原始 SQL 语句 | 日志小 | NOW()、UUID()、LIMIT 等非确定性语句会导致主从不一致 |
| ROW | 每行变更前后镜像 | 一致性最强 | 大批量更新日志膨胀严重 |
| MIXED | 按语句自动选择 | 折中 | 仍有边缘不一致风险 |
生产建议:统一使用 ROW 格式(binlog_format=ROW)。现代磁盘和网络下日志体积已不是主要瓶颈,数据一致性价值远高于存储成本。
二、异步复制搭建实战
1. 主库配置(my.cnf)
[mysqld]
server-id=1
log-bin=mysql-bin
binlog_format=ROW
binlog_row_image=FULL
expire_logs_days=7
# 保证崩溃安全
innodb_flush_log_at_trx_commit=1
sync_binlog=1
# GTID(强烈建议开启,简化故障切换)
gtid_mode=ON
enforce_gtid_consistency=ON
log_replica_updates=ON
2. 创建复制账号
CREATE USER 'repl'@'10.0.%' IDENTIFIED BY 'ReplStrongPwd2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.%';
FLUSH PRIVILEGES;
3. 从库配置并建立复制
[mysqld]
server-id=2
relay-log=relay-bin
read_only=ON
gtid_mode=ON
enforce_gtid_consistency=ON
log_replica_updates=ON
使用 GTID 后建立复制极其简洁,不再需要传统的 binlog 文件位点:
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='10.0.0.10',
SOURCE_PORT=3306,
SOURCE_USER='repl',
SOURCE_PASSWORD='ReplStrongPwd2026',
SOURCE_AUTO_POSITION=1;
START REPLICA;
4. 验证复制状态
SHOW REPLICA STATUS\G
关键看两项都为 Yes:
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 0
Retrieved_Gtid_Set / Executed_Gtid_Set # 与主库对齐
三、半同步复制:数据安全的折中
异步复制存在数据丢失窗口:主库提交后立即返回客户端,若此时主库崩溃且 binlog 尚未传到从库,这部分事务永久丢失。半同步复制(semi-sync)要求至少一个从库确认收到 binlog 后主库才返回:
# 主库
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;
SET GLOBAL rpl_semi_sync_source_timeout = 3000; # 毫秒
# 从库
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = 1;
半同步在从库超时未确认时会自动退化为异步,保证可用性。配合 rpl_semi_sync_source_wait_for_replica_count 可控制需要等待几个从库,在数据安全和延迟之间权衡。
四、读写分离架构
主从拓扑就绪后,读流量打到从库、写流量留在主库。实现方式分三类:
1. 中间件代理(推荐生产)
ProxySQL 是最主流的方案,它理解 MySQL 协议,能基于查询内容路由:
# 配置主从组
INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES
(0,'10.0.0.10',3306), # HG0 写组
(1,'10.0.0.20',3306), # HG1 读组
(1,'10.0.0.21',3306);
# 路由规则:除 SELECT 外全部走写组
INSERT INTO mysql_query_rules(rule_id,match_digest,destination_hostgroup,apply)
VALUES
(1,'^SELECT.*FOR UPDATE$',0,1),
(2,'^SELECT',1,1);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
ProxySQL 还能自动探测从库复制延迟,延迟超过阈值的从库会被临时踢出读组。
2. 应用层路由
框架层面区分读写连接,如 ShardingSphere-JDBC、Spring 的 AbstractRoutingDataSource。优点是少一跳网络,缺点是多语言栈难统一。
3. 连接级路由
简单场景用两个数据源配置:写库一个连接池、读库一个连接池,业务代码显式选择。适合规模小、查询模式清晰的系统。
五、复制延迟的成因与治理
Seconds_Behind_Source 是读写分离架构的核心风险指标。从库读到旧数据会造成业务异常(如刚下单查不到订单)。常见成因:
- 单线程重放瓶颈:MySQL 5.7 前 SQL 线程是单线程,主库高并发写入时从库追不上
- 大事务:一个 UPDATE 改百万行,从库重放耗时长
- 从库硬件差:从库常被当作”备份机”低配部署,IO 能力不足
- 长链路锁等待:从库上的读查询与重放线程争锁
治理手段:开启多线程复制(基于 WRITESET 并行):
STOP REPLICA;
SET GLOBAL replica_parallel_workers=8;
SET GLOBAL replica_parallel_type='LOGICAL_CLOCK';
SET GLOBAL binlog_transaction_dependency_tracking='WRITESET';
START REPLICA;
WRITESET 模式通过分析事务修改的行集合判断冲突,能让本来串行的事务在从库并行重放,大幅降低延迟。
六、主从切换与高可用
主库故障时需要把一个从库提升为主。手工切换流程:
# 1. 确认从库已追平(GTID 无差异)
SHOW REPLICA STATUS\G
# 2. 从库停止复制并解除只读
STOP REPLICA;
RESET REPLICA ALL;
SET GLOBAL read_only=OFF;
SET GLOBAL super_read_only=OFF;
# 3. 其他从库指向新主
CHANGE REPLICATION SOURCE TO SOURCE_HOST='10.0.0.20', SOURCE_AUTO_POSITION=1;
START REPLICA;
生产环境应使用自动化工具,避免人工在凌晨故障时手抖:
- Orchestrator:GitHub 开源,自动检测拓扑、支持恢复钩子,是最主流的主从管理工具
- MHA:经典方案,能从多个从库中补齐差异 binlog,但项目维护趋缓
- MySQL InnoDB Cluster:官方方案,Group Replication + MySQL Router + MySQL Shell,开箱即用
七、常见故障排查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| IO 线程 Connecting | 网络/防火墙、复制账号权限、密码错误 | 检查 3306 连通性、SHOW GRANTS |
| SQL 线程 Errno 1062 | 主键冲突(从库被误写) | pt-table-checksum 查差异,pt-table-sync 修复 |
| SQL 线程 Errno 1032 | 行不存在(ROW 格式找不到要更新的行) | 数据已漂移,需重新校验同步 |
| GTID 冲突 Errno 1062 | 从库执行过本地写事务 | gtid_executed 注入空事务跳过,根治靠 super_read_only |
关键纪律:从库必须设置 super_read_only=ON。普通 read_only 对 SUPER 权限用户无效,仍可能被误写导致数据漂移。
八、数据一致性校验
复制正常运行不等于数据一定一致。应定期用 Percona Toolkit 校验:
# 主库建校验
pt-table-checksum h=10.0.0.10,u=checksum,p=*** --replicate=percona.checksums
# 从库查看差异
pt-table-checksum h=10.0.0.20,u=checksum,p=*** \
--replicate=percona.checksums --replicate-check-only
# 修复差异(先备份!)
pt-table-sync --execute h=10.0.0.10 h=10.0.0.20 \
u=checksum,p=*** --replicate=percona.checksums
总结
MySQL 复制体系的演进路径很清晰:异步复制解决容量和备份,半同步堵住数据丢失窗口,GTID 简化拓扑管理,读写分离扩展读吞吐,Orchestrator/InnoDB Cluster 实现自动故障转移。落地顺序建议:先建 ROW+GTID 异步复制 → 加 ProxySQL 读写分离 → 上半同步 → 最后接入自动切换。每一步都验证稳定后再走下一步,比一上来就堆全套复杂方案可靠得多。