ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MySQL主从复制与读写分离实战:从零搭建到故障切换

2026/9/14 9:06:49 拓冰建站 浏览量
MySQL主从复制与读写分离实战:从零搭建到故障切换 很多做后端开发的朋友都有过这样的经历单机 MySQL 跑到一定阶段慢查询变多备份任务一跑就锁表业务高峰期主库 CPU 直接飙红。这时候网上搜一圈满屏都是主从复制 读写分离感觉像是灵丹妙药。可真到自己动手搭的时候才发现坑远比想象的多——配置参数一堆从库数据不一致主库宕机了不知道怎么办中间件选型也纠结半天。这篇博文不跟你讲虚的直接把我自己从零搭建 MySQL 主从复制、再落地读写分离的完整过程梳理出来。会讲到复制到底是怎么工作的、binlog 和 relay log 的数据流、核心参数怎么配、GTID 和传统位点复制怎么选、读写分离用代码路由还是中间件、主从延迟怎么监控和规避、主库宕机后怎么切换以及我实测踩过的那些坑。适合已经能熟练操作单机 MySQL、正准备往高可用架构迈一步的工程师也适合准备面试被问到主从复制原理这类问题时想有个系统认知的同学。1. 主从复制解决了什么问题又带来了什么新麻烦先说清楚一件事主从复制不是万能的它解决的是特定场景下的问题。你只有先搞清楚它能干什么、不能干什么后面配置起来才不容易跑偏。1.1 单机数据库的真实瓶颈我见过太多团队业务量还没起来的时候单库单表跑得挺欢直到某天出现下面三个信号才开始焦虑第一个信号是读压力压垮主库。一个典型业务读请求和写请求的比例轻轻松松到 10:1 甚至更高。你想想看用户刷首页、查订单、看商品详情全都是读操作。这些请求全打到同一个 MySQL 实例上CPU 和 IO 迟早扛不住。第二个信号是备份影响线上业务。用 mysqldump 做逻辑备份或者用 XtraBackup 做物理备份都会对源库产生额外的 IO 负载和锁竞争。凌晨两三点跑备份业务低峰期还好可一旦业务是 24 小时全球化的备份和在线业务直接打架。第三个信号是单点故障。一台机器挂了整个服务全挂没有任何冗余。硬件故障、机房断电、运维误操作任何一个都可能导致服务长时间不可用。主从复制的核心思路很简单一个主库负责写多个从库负责读。写请求依然走主库读请求分摊到从库。这样主库的压力骤减备份也可以专门挑一个从库来跑不影响主库业务同时从库天然成为了数据的热备份。听起来很美好对吧但这个架构一旦引入你就要接受它带来的一堆新问题。1.2 复制架构引入后的额外成本我能想到的最贴切类比是单机数据库像一个干杂活的个体户什么都能干但能力有上限主从架构就像一个团队有领导有下属能接更大的盘子但团队管理本身就有成本。第一个成本是数据一致性。主库写入了从库还没来得及同步读请求就可能读到旧数据。这个问题叫主从延迟业务上必须容忍一定的数据滞后。你不能要求刚下单就能立刻在订单列表里看到虽然大多数情况下这个延迟只有几十毫秒。第二个成本是架构复杂度。原来连接一台数据库就行现在要维护主库和从库两套环境复制链路断了要排查从库数据跟主库不一致要修复重做主库挂了要把从库提升为主库……这些都是运维层面的额外负担。第三个成本是脑裂风险。如果网络分区或者运维误操作可能出现两个节点都认为自己是主库的情况这时候两个节点都在写入数据就会产生不可修复的分叉。这是主从架构里最危险的问题之一容不得半点马虎。搞清楚了这些你才能理解后面每一步操作背后的动机。主从复制的配置本身不难难的是把整个链路的细节都照顾到。2. 复制机制核心拆解binlog 与 relay log 的数据流转配置 MySQL 主从之前你得先明白 replication 到底做了什么事。这个机制不算复杂但每个角色各司其职环环相扣。2.1 三个线程的接力赛MySQL 主从复制本质上是三个线程的协作主库上的Binlog Dump Thread负责把主库的二进制日志binlog发送给从库。从库上的I/O Thread负责接收主库发来的 binlog并写入到从库本地的中继日志relay log。从库上的SQL Thread负责读取 relay log 中的事件并逐个在从库上重放执行。用大白话说主库是一个出版社binlog 是它印好的报纸从库的 I/O Thread 是快递员把报纸从出版社搬回自己家的仓库relay log从库的 SQL Thread 是读者把仓库里的报纸一份份拆开看重放执行。搬报纸和读报纸是两个独立的过程这就意味着从库接收 binlog 的速度和重放 SQL 的速度是可以不一致的——这也是主从延迟产生的最底层机制。MySQL 8.0 之后实际上是把 SQL Thread 进一步拆成了 Coordinator协调线程和多个 Worker工作线程这就是多线程复制MTS目的是加快 relay log 的重放速度从而降低主从延迟。这个机制我后面会专门讲。2.2 binlog 的三种格式与选型binlog 是复制的基础它的记录格式直接决定了复制的正确性和效率。MySQL 支持三种格式STATEMENT记录原始 SQL 语句。日志量小但某些非确定性函数如 NOW()、UUID()会导致主从数据不一致。ROW记录每行数据的变更前后镜像。最安全、最准确但日志量会成倍增加。MIXEDMySQL 自动判断默认用 STATEMENT遇到不安全语句自动切换为 ROW。算是一种折中。我的建议是直接上 ROW 格式。虽然日志会大一些但 ROW 格式在数据一致性上最可靠。尤其是涉及 UPDATE 或 DELETE 影响多行数据时STATEMENT 格式在从库重放可能因为索引选择不同而产生意外而 ROW 格式是按主键或实际影响行来重放几乎没有歧义。磁盘不值钱数据错乱可是要命的。2.3 复制方式的演进位点复制 vs GTID 复制理解了 binlog 之后另一个关键概念是复制定位方式。早期 MySQL 采用基于日志文件位点FilePosition的复制。从库需要记录主库当前 binlog 的文件名和偏移量position复制链路从那个位点开始。这种方式配置比较繁琐尤其是从库落后太多、或者换新从库时要找到正确的位点非常麻烦特别容易出错。MySQL 5.6 引入了GTIDGlobal Transaction Identifier全局事务标识符复制。每个事务在提交时会被分配一个全局唯一的 ID由UUID:序号组成。从库通过 GTID 就能知道哪些事务已经执行过、哪些还没执行主从重连或者切换时不需要再关心 binlog 文件名和位置。如果你是从零开始搭建新环境务必直接使用 GTID 复制。我后面演示的配置就是 GTID 方式。它不光配置简单更重要的是在主从切换、故障恢复时比位点复制省心太多。3. 从零搭建一主一从基于 MySQL 8.0 的完整实操现在进入正题。我用的是 MySQL 8.0 版本操作系统是 CentOS 7/8 这类 Linux 环境。如果你用的是免安装版或者压缩版配置文件的路径可能不太一样但核心配置项是一样的跟着改就行。3.1 环境准备与基础配置假设有两个节点主库192.168.1.101端口 3306从库192.168.1.102端口 3306两台机器都装了 MySQL 8.0建议版本尽量一致。如果版本不一致至少要保证从库版本不低于主库否则可能出现 binlog 格式不兼容的问题。我在测试环境里就遇到过主库 8.0.36、从库 8.0.43 的情况高版本从库复制低版本主库工作正常反过来低版本从库接收高版本主库的 binlog 就报错当时排查了半天。主库的my.cnf增加以下配置[mysqld] # 服务唯一标识集群内必须唯一 server_id 101 # 开启 binlog命名前缀自定义 log_bin mysql-bin # 使用行格式记录保证数据一致性 binlog_format ROW # binlog 保留天数按磁盘容量调整 binlog_expire_logs_seconds 604800 # 开启 GTID 模式 gtid_mode ON enforce_gtid_consistency ON # 作为主库其他节点可以从本机复制从库的my.cnf增加以下配置[mysqld] # 服务唯一标识不能和主库相同 server_id 102 # 开启 relay log名字自己起 relay_log mysql-relay-bin # 只读模式防止应用误写从库 read_only ON # 同样开启 GTID gtid_mode ON enforce_gtid_consistency ON两个很容易忽略的细节需要特别说明一下第一server_id绝对不能重复。我在跟别人一起排查问题时遇到过两台机器server_id都配成 1 的情况结果从库日志里一直报Slave I/O thread连接中断主库那边也不断报错。这个参数不需要全局唯一但同一个复制拓扑里必须每个节点不同哪怕是多级复制链也同理。第二read_only不等于super_read_only。read_only对普通用户生效但对具有 SUPER 权限的用户不生效。DBA 日常维护还是要用超级账号写入的所以如果想让从库绝对不能被任何客户端写入加上super_read_only ON更保险。不过要注意复制线程本身不受这几个参数影响从库的 SQL Thread 重放 relay log 时照样能写进数据。配置文件改完重启 MySQLsystemctl restart mysqld。3.2 在主库创建专用复制账号复制链路需要一个账号去主库拉取 binlog。千万别图省事直接用 root用最小权限账号是基本原则。这个账号在从库连接主库时用到CREATE USER repl192.168.1.% IDENTIFIED BY 强密码_请替换; GRANT REPLICATION SLAVE ON *.* TO repl192.168.1.%; FLUSH PRIVILEGES;这里只授予了REPLICATION SLAVE权限这个权限只允许该账号请求 binlog dump没有其他任何操作权限安全风险可控。注意192.168.1.%这个网段限制别偷懒写成%把账号暴露在公网上是给自己埋雷。3.3 初始化从库数据避免直接复制数据目录这是最容易踩坑的一步。很多新手直接拿主库的数据目录打个 tar 包丢到从库然后配置复制开始。这不是不行但前提是你必须保证打包数据那一刻和复制起始位点严格对应否则从库应用 binlog 的时候会出现找不到行的错误。而且 8.0 版本的数据字典和 redo log 内部结构复杂直接拷贝文件时如果有版本差异基本必炸。正确做法是使用mysqldump或者XtraBackup做一次一致性快照。从库还没启动复制前先用工具把主库的数据导出来导入从库然后从快照对应的 binlog 位点开始复制。用mysqldump的操作步骤是在主库执行mysqldump -uroot -p --all-databases \ --single-transaction \ --set-gtid-purgedON \ --master-data2 \ backup.sql参数解释--single-transaction使用 InnoDB 事务的快照进行一致性备份不影响业务写入也不需要锁表。这个参数只对 InnoDB 表有效如果你的库里还有 MyISAM 表那还是会在备份期间锁表的。--master-data2在备份文件的头部注释里记录备份时刻的 binlog 位点信息方便定位复制起点。--set-gtid-purgedONGTID 模式下推荐开启这样从库导入时就知道哪些事务已经在数据里了复制启动后会自动从缺失的 GTID 开始执行。然后把备份文件传到从库导入mysql -uroot -p backup.sql用 XtraBackup 的方式稍微不同它是物理备份速度更快适合大数据量场景。核心步骤大概是# 在主库执行全量备份 xtrabackup --backup --target-dir/data/backup --host127.0.0.1 --userroot --passwordxxx # 做恢复准备 xtrabackup --prepare --target-dir/data/backup # 把备份目录传到从库替换从库数据目录 xtrabackup --copy-back --target-dir/data/backup无论哪种初始化方式最后你都要从备份里找到对应的 GTID 位点。mysqldump的--master-data2会把位点信息写在 SQL 文件头部的注释里类似这样-- CHANGE MASTER TO MASTER_LOG_FILEmysql-bin.000003, MASTER_LOG_POS154;如果开了 GTID还会看到SET GLOBAL.GTID_PURGEDuuid:1-100这样的行。从库导入数据时 MySQL 会自动设置gtid_purged所以在从库执行CHANGE MASTER TO时不用再手动指定 binlog 文件名和位置了。3.4 在从库配置复制链路数据初始化好了接下来在从库执行复制配置命令CHANGE MASTER TO MASTER_HOST192.168.1.101, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORD强密码_请替换, MASTER_AUTO_POSITION1;注意MASTER_AUTO_POSITION1这就表示走 GTID 自动定位不需要手动指定MASTER_LOG_FILE和MASTER_LOG_POS。这个参数是 GTID 复制的精髓你以后做从库提升、failover 切换时都会受益。然后启动复制START SLAVE;这里有个细节需要提醒MySQL 8.0.22 之前的版本用的是START SLAVE和SHOW SLAVE STATUS8.0.22 之后官方推荐用START REPLICA和SHOW REPLICA STATUS并且SLAVE开头的命令被标记为 deprecated虽然还能用。为了长期兼容建议你直接在 8.0 环境里用REPLICA系列的写法。检查复制状态SHOW REPLICA STATUS\G重点观察两列Replica_IO_Running: YesI/O 线程正常说明已经在从主库拉取 binlog 并写入 relay log。Replica_SQL_Running: YesSQL 线程正常说明 relay log 里的语句正在被重放。如果两边都是Yes恭喜你复制链路已经跑通了。Seconds_Behind_Source这一列会显示从库落后主库多少秒刚启动时可能会有一个短暂的较大延迟等追平后就会变成 0 或者一个很小的值。为了验证复制真的生效你可以在主库建一张测试表插几行数据然后到从库查一下-- 主库执行 CREATE DATABASE test_repl; USE test_repl; CREATE TABLE t_user (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50)); INSERT INTO t_user (name) VALUES (zhangsan), (lisi);然后到从库USE test_repl; SELECT * FROM t_user;能看到zhangsan和lisi两行说明整个链路完全打通了。千万不要跳过这一步我见过太多人配置完不验证过了两天发现数据根本没同步原因是 binlog 没开或者网络不通白白浪费时间。4. 读写分离落地选型应用层路由还是中间件主从搭好了下一步就是把读写请求分流了。这一节我把实际工程里用过的两条路线都讲清楚你根据自己团队的情况选。4.1 应用层路由侵入业务代码的方案最简单粗暴的读写分离方式是在应用代码里维护主库和从库两套数据源。可以用 Spring 之类的框架配合动态数据源切换实现核心逻辑是写操作和事务操作走主库数据源读操作走从库数据源。我见过很多小型项目用这种方式优点非常明显不引入额外组件架构简洁排查问题直接看应用日志就行。缺点也很突出读写分离逻辑和业务代码耦合在一起每个需要走从库的查询都要显式指定或者靠 AOP 切面判断一旦从库数量变化、或者某个从库挂了应用层要感知并处理故障代码量会迅速膨胀。适合场景团队规模小、没有专职 DBA、业务并发量还没到需要横向扩展从库的程度过渡期用一用完全 OK。4.2 数据库中间件对业务透明的方案当业务增长到一定规模应用层路由的维护成本会逐渐失控这时候就该上数据库中间件了。市面上主流的中间件我在下面表里列一下方便你横向对比中间件协议兼容性读写分离支持分库分表治理能力备注MyCatMySQL 协议支持支持一般老牌中间件社区活跃度下降新项目慎选ShardingSphere-JDBC原生 JDBC 接入支持支持强以 SDK 形态嵌入应用代码侵入小配置灵活ShardingSphere-Proxy透明 MySQL 协议支持支持强独立部署代理对应用完全透明适合多语言ProxySQLMySQL 协议支持不支持强轻量、性能好专为读写分离和连接池优化如果只在 MySQL 生态内做读写分离、不想过渡设计ProxySQL 是我用得最顺手的一个。它的配置灵活支持在线启停、查询路由规则、连接池管理还能根据实例健康状态自动剔除故障节点。缺点是不支持分库分表但这不是它该干的活。ShardingSphere-Proxy 适合那种既要读写分离、又预见到未来要分库分表的团队。它可以让应用层始终连一个标准 MySQL 协议端口后面集群怎么变对应用无感知。缺点是多了中间层链路变长对排障能力要求高。4.3 我的选型建议给一个直接的结论如果你的业务库读写比小于 5:1从库最多 2 个团队也小用应用层路由就够了别为了架构而架构。如果读写比很高从库要扩到 3 个以上而且希望自动故障转移直接上 ProxySQL日后再觉得不够再上 ShardingSphere。如果你们明确未来会碰到单表数据量过大、需要水平拆分的点那就一步到位用 ShardingSphere 的生态。4.4 ProxySQL 读写分离配置速览如果你选了 ProxySQL最简单的配置方式是在 ProxySQL 里定义两个组写组和读组。然后插入一条 query rule把SELECT语句路由到读组其余语句路由到写组。-- 添加后端节点到 ProxySQL INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (10, 192.168.1.101, 3306); INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (20, 192.168.1.102, 3306); -- 配置读写组 INSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup) VALUES (10, 20); -- 设置监控账号ProxySQL 用它检查后端健康状态 UPDATE global_variables SET variable_valuemonitor_user WHERE variable_namemysql-monitor_username; UPDATE global_variables SET variable_value监控密码 WHERE variable_namemysql-monitor_password; -- 路由规则SELECT 默认走读组 INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, ^SELECT, 20, 1); -- 应用修改 LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK; LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;这里有很多细节可以深化。比如match_pattern用的是正则一个简单的^SELECT会把SELECT ... FOR UPDATE也路由到读组这是致命的——FOR UPDATE是加锁读必须走主库。所以规则要写成^SELECT.*FOR UPDATE单独建一条规则并优先匹配或者用 ProxySQL 的digest精确匹配。另外事务内的读也应该走主库否则会出现同一个事务里先写后读读到的是旧值这种尴尬。用 ProxySQL 的话可以在连接层做判断也可以在应用层把事务连接固定到写库。这些都是生产环境里真实会遇到的问题配的时候别只图简单。5. 主从延迟成因、监控与规避手段主从复制搭完最大的敌人就是延迟。从库数据落后主库读请求就会读到旧数据轻则用户体验下降重则业务逻辑出错。这一节我把延迟的成因和应对方法讲透。5.1 为什么会有延迟延迟的根源可以从两个维度理解网络传输和 SQL 重放。网络传输方面的延迟一般很小除非主从机房跨地域带宽受限。真正的大头在 SQL 重放。主库是并发写入的比如有 8 个连接同时执行 INSERT8 个事务并行提交而传统复制里从库只有单线程重放就相当于一条流水线要处理 8 条流水线的活自然就积压了。这就是 MySQL 5.6 之前从库延迟特别严重的根本原因。MySQL 5.7 引入了基于库级别并行的多线程复制MySQL 8.0 的 MTSMulti-Threaded Slave基于 Writeset 进一步优化同一个事务组内没有冲突没有改同一行的事务可以并行重放延迟问题得到了很大缓解。但 Writeset 有个前提表必须有主键。没有主键的表更新要全表扫描找目标行从库性能直线下降这是很多线上从库延迟飙升的隐形凶手。5.2 延迟的监控方法先泼一盆冷水SHOW REPLICA STATUS里的Seconds_Behind_Source字段并不是一个完全可信的指标。它是靠从库当前执行时间和 I/O 线程接收时间的差值估算的一旦 SQL 线程卡住或者 relay log 特别大这个值可能为 0但实际数据差了一大截。我常用的监控策略是组合拳第一招对比主从 GTID 位置。-- 主库执行 SHOW MASTER STATUS; -- 从库执行 SHOW REPLICA STATUS\G看从库的Retrieved_Gtid_Set和Executed_Gtid_Set如果两者差值持续增大说明从库接收得很快但重放跟不上SQL 线程是瓶颈。第二招窗口比较法。在从库创建一张心跳表定时更新一个时间戳再用业务查询读到的时间戳和当前时间比较。比如每隔 1 秒在主库写入UPDATE heartbeat SET tsNOW()从库读出ts和本地时间的差值就是相对精确的复制延迟。这个方法能精确反映业务可见的延迟比看内部状态字段直观得多。第三招监控 relay log 积压量。看从库的Relay_Log_File对应的 relay log 文件大小以及Relay_Log_Pos的增长速度。如果文件持续膨胀意味着 SQL 线程卡住或者速度太慢。5.3 延迟的规避方案延迟完全消除不现实但把它控制到业务可接受范围内是能做到的。最核心的一条尽量让所有业务表都有主键。这不仅是延迟问题也关系到复制稳定性和数据一致性。我查过不少从库报错Could not execute Write_rows event on table ...十有八九是目标表没有主键ROW 格式下无法唯一定位行。第二条开启从库多线程复制。MySQL 8.0 默认基于 Writeset 的并行复制需要确认SHOW VARIABLES LIKE slave_parallel_workers; SHOW VARIABLES LIKE slave_parallel_type;如果slave_parallel_workers还是默认的 0改成 4 或者 8按 CPU 核心数调slave_parallel_type设为LOGICAL_CLOCK。改完要重启从库生效。第三条也很常见避免在从库上执行大事务。虽然从库是只读的但如果你不小心跑了一个大查询占用了大量 IO 和 CPUSQL Thread 重放进展会立刻变慢。从库的硬件配置最好不低于主库读写分离的本质就是把读压力转移到从库从库磁盘不行延迟照样高。第四条把二级索引建好。ROW 格式的复制在从库重放 UPDATE 和 DELETE 时需要先定位目标行。如果 WHERE 条件无法走索引从库会全表扫描主库可能走索引只锁几行从库却要扫全表。这种差异在数据量大时非常致命。6. 故障切换与高频踩坑主库宕机、数据不一致怎么办复制架构里做得再好故障总会来的。这一节把我遇到过的典型故障和对应处理完整走一遍这些事情在课本和官方文档里写得很简略但实际做起来处处是坑。6.1 主库宕机后的手动切换流程在主从架构下如果主库物理宕机或者数据损坏最简单的恢复方案是手动把从库提升为新主库然后让其他从库如果有重新指向它。流程如下第一步确认主库状态。如果只是网络分区主库没挂还在写数据此时贸然提升从库会造成脑裂。先登录主库看看能不能写查一下SHOW MASTER STATUS是否正常返回。如果连不上或者异常才进入切换流程。第二步确认从库数据追上主库。在从库上执行SHOW REPLICA STATUS\G检查Seconds_Behind_Source是否为 0以及Retrieved_Gtid_Set是否等于Executed_Gtid_Set。如果从库数据落后很多而主库已经起不来了那你只能接受丢失最后一段数据的现实把当前从库顶上去。如果还有另一台健康的从库可以先把数据从这台顶上之前尽量补齐再切换。第三步停止复制并提升从库为可写STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only OFF; SET GLOBAL super_read_only OFF;第四步把业务连接切到新主库。这一步如果用的是 ProxySQL直接在配置里把主从组的节点对调即可如果用应用层路由就改数据源配置。切换后别忘了让其他从库如果有指向新的主库CHANGE MASTER TO MASTER_HOST192.168.1.102, MASTER_USERrepl, MASTER_PASSWORD强密码, MASTER_AUTO_POSITION1; START REPLICA;这里有个关键点GTID 模式下新主库的Executed_Gtid_Set已经包含了旧主库所有已执行事务其他从库只要用MASTER_AUTO_POSITION1去对接MySQL 会自动跳过那些已经执行过的事务完成无缝衔接。这就是 GTID 对比位点复制的最大优势之一。如果你需要把旧主库修复后重新加回集群它会作为新主库的从库。因为 GTID 包含了整个集群的历史它启动复制后会自动跳过自身已执行的事务只拉取新增的数据。整个过程非常顺滑。6.2 常见故障案例与根因我把高频踩坑整理成一个表每一行都是真实发生过的问题问题现象直接原因解决方式Slave_IO_Running: Connecting网络不通、账号权限错误、server_id 重复检查防火墙 3306 端口、确认账号权限、核对 server_idSlave_SQL_Running: Norelay log 里有错误 SQL比如表结构不一致、主键冲突SHOW REPLICA STATUS看Last_SQL_Error修复后人工STOP REPLICA再START REPLICA或跳过后继续主从数据不一致但复制状态正常binlog_format 为 STATEMENT或从库有应用写入改用 ROW 格式给从库加read_onlysuper_read_onlyDDL 在从库执行特别慢主库 8 并发 DDL从库单线程串行重放低峰期执行 DDL或者临时调大slave_parallel_workers大事务如一次性 DELETE 百万行导致延迟飙升ROW 格式记录了大量变更事件重放耗时拆分为分批操作避免单个事务过大6.3 数据不一致的检测与修复复制状态正常并不代表数据一定一致。你可以用pt-table-checksum这个工具做周期性的主从数据一致性校验。它的原理是把大表分成多个 chunk在每个 chunk 上计算校验和然后对比主从的校验和结果。发现不一致后再用pt-table-sync修复。这两个工具是 Percona Toolkit 里最常用的值得提前装好。提示pt-table-sync修改数据之前务必先备份。工具默认会生成变更语句你可以先看输出再决定是否执行别直接一股脑同步过去否则可能扩大事故范围。6.4 关于半同步复制的补充异步复制在极端情况下主库崩了binlog 还没来得及传给从库会丢数据。对数据零容忍的业务可以启用半同步复制Semisynchronous Replication。半同步复制的原理是主库提交事务时需要等待至少一个从库确认收到 binlog 并写入 relay log 后才向客户端返回提交成功。这样能最大程度保证主库已提交的事务不会因为主库挂了就丢失。代价是写入性能会受影响因为多了一次网络往返等待。MySQL 8.0 里半同步是插件式安装。想深入研究的可以查看官方文档里的INSTALL PLUGIN rpl_semi_sync_source和rpl_semi_sync_replica相关说明。在性能和数据安全之间怎么取舍取决于你对 RPORecovery Point Objective恢复点目标的要求——允许丢最近多少秒的数据这个问题要在架构设计时就想清楚。7. 我的实操总结与几个额外忠告一路写到这里最后分享几点我反复踩坑后沉淀下来的经验。这些不是官方文档里会教你的东西但每一条都是真金白银换来的。第一个忠告搭建阶段的花费时间是值得的。很多同学急着看效果跳过数据一致性初始化直接 CHANGE MASTER 顺手就 START。等到从库Last_Errno报错又回头重新初始化反而浪费更多时间。严格按照先备份一致性快照、再导入从库、再启动复制、最后验证的流程走一次成功概率几乎百分之百。第二个忠告监控一定要前置。主从复制不是配置完就一劳永逸的binlog 文件会膨胀、网络会闪断、磁盘会写满、大事务会堵塞重放。配好之后第一时间做监控至少包括复制状态IO/SQL 线程是否 Running、复制延迟心跳表检测、relay log 积压量、主从磁盘空间。监控告警到位了你才有处理问题的预警时间而不是等业务反馈数据不对了才去查。第三个忠告面试和实战是两回事。网上有很多 MySQL 面试题把主从复制的原理背得滚瓜烂熟但真要上手配置时各种报错。原理当然要懂但更重要的是亲手把它搭出来、踩一遍坑、再思考为什么。当你真正经历过一次主库挂了、手动切到从库、业务恢复的完整过程你对这套机制的认知才会真正从会背变成会做。关于这套架构后续还能怎么演进我觉得值得留意的方向是 MySQL 组复制Group Replication和 InnoDB Cluster。组复制能做到多主写入、自动选主在数据一致性和高可用性上比传统主从复制更进一步。如果你是刚接触主从复制建议先把今天这套单主多从玩明白然后再往组复制的方向探索——基础不牢的时候上更复杂的架构只会让你在故障面前手足无措。