最近在一个内部交流群里,一位做后端开发的同事抛了个让人头大的问题:主库一切正常,从库的Seconds_Behind_Master却一直往上飙,业务那边催着出数据,他对着show slave status的输出一头雾水。我隔着屏幕都能感受到那种状态——MySQL 主从复制这套东西,文档里写得“配置简单、开箱即用”,可真到了线上出问题时,原理摸不透,排查起来就是两眼一抹黑。
这篇内容,我打算把 MySQL 主从复制架构彻底拆开揉碎,讲清楚一条 binlog 从主库写到从库、再从从库落地的完整链路,同步覆盖异步、半同步、组复制几种模式的取舍,以及从零搭建一主一从的完整过程和日常运维中最高频的坑。特别适合刚接手生产库运维、或者在读写分离架构里踩过坑、想系统弄懂复制原理的后端开发者和 DBA。
1. 主从复制架构的核心拆解
1.1 复制的最小闭环:一条 binlog 的旅程
很多人对主从复制的理解停留在“主库写、从库同步”这个层面,但真让你把底层链路画出来,可能就含糊了。先给一个最基本的闭环概念:主库把数据变更写入二进制日志(binlog),从库通过网络拉取这些日志,然后在自己本地按顺序重放一遍,从而实现数据一致。
这个闭环可以类比成快递派送:主库是发货仓,binlog 是发货单,从库的 IO 线程是收货员,relay log(中继日志)是快递站里的临时货架,SQL 线程则是负责把包裹拆开、按单上架的理货员。
整个流程拆细了就是四个角色在接力:
- 主库上的客户端请求执行写事务,InnoDB 存储引擎提交事务的同时,把变更记录写入 binlog 文件。
- 主库上有一个专门的 dump 线程,负责监听 binlog 文件的变化,并把新增的日志推送给已经连接上来的从库。
- 从库的 IO 线程接收主库推送过来的日志,把它们原样写入本地的 relay log,并记录自己已经接收到的坐标。
- 从库的 SQL 线程读取 relay log,把里面的 SQL 事件在从库上顺序执行一遍,完成数据回放。
这里有个很多人第一次接触时会问的点:为什么不直接从主库的 binlog 执行,非得中间加一层 relay log?答案是解耦。SQL 线程的执行速度通常比网络拉取慢得多,尤其是遇到大事务的时候。relay log 相当于一个缓冲地带,IO 线程可以先拼命把数据拉回来,SQL 线程再按自己的节奏慢慢应用,两边谁都不会阻塞谁。另外,从库一旦崩了或重启,只需要知道 relay log 应用到了哪个位置,就能继续干活,不用再回头去主库重新拉一大段历史日志。
1.2 两个线程各自忙什么:IO 线程和 SQL 线程的分工
从库上这两个线程经常被人混在一起讲,但它们的职责、瓶颈和在show slave status里的状态表现都完全不同。
IO 线程的核心任务只有一个:从主库拿日志、写 relay log。这个线程处理的是网络 IO 和顺序写文件,正常情况下它非常快,几乎不会成为瓶颈。它在执行过程中会定期向主库报告自己需要的 binlog 文件号和偏移量,主库 dump 线程根据这个坐标,从指定的位置把日志继续推给从库。
SQL 线程的核心任务是回放日志。这个线程把 relay log 里的二进制事件解析成 SQL 语句或行数据变更,并在从库上执行。因为是本地执行,它涉及事务提交、索引更新、buffer pool 操作,这些都可能让它比 IO 线程慢很多。默认配置下 SQL 线程是单线程运行的,这也是老版本 MySQL 主从复制最容易出现延迟的根源。
在show slave status输出里,可以看到Slave_IO_Running和Slave_SQL_Running这两个字段。它们只告诉你“线程有没有挂掉”,但Seconds_Behind_Master则能给你一个延迟的直观数值。这个值的计算逻辑,可以理解成 SQL 线程当前正在执行的 binlog 事件的写入时间戳,和 IO 线程已经拉到的最新 binlog 事件写入时间戳之间的差值。如果 SQL 线程卡在一个大事务上,这个值会快速增大,看起来十分吓人。
需要强调一点,Seconds_Behind_Master并不是一个精确的“实时延迟秒数”,它更像一个心跳式的估算值。更靠谱的判断方法,是手动对比主从两边某个已知表的当前数据量或 max(id),拿间歇性查询结果做差值。
1.3 看懂 show slave status 里的核心字段
每次排主从问题,第一步就是执行show slave status\G,然后把输出里那些关键字段看清。这里挑几个最能定位问题的字段,按照“同步进度”和“错误信息”两类分开看。
同步进度相关的:
Master_Log_File和Read_Master_Log_Pos:表示 IO 线程已经从主库拉取到了哪个 binlog 文件和哪个偏移量。这两个值和主库上当前的 binlog 状态对比,就能看出网络层是否有积压。Relay_Master_Log_File和Exec_Master_Log_Pos:表示 SQL 线程已经执行到了主库 binlog 的哪个位置。这两个值跟上一条的两个值之间的差值,就是当前真正的积压距离。Relay_Log_File和Relay_Log_Pos:表示 SQL 线程读到了本机哪个 relay log 文件的哪个位置,刚才两条已经说明了主从位置,这一组是最底层的本地落点。Seconds_Behind_Master:上面解释过,是常用的延迟参考值。当它显示 NULL 时,通常意味着 SQL 线程还没开始执行,或者 IO 线程没有在工作。
错误信息相关的:
Last_IO_Errno和Last_IO_Error:IO 线程最近一次报错。常见的如网络连接被重置、主库 binlog 被清理导致请求的位置不存在、账号权限不足等。Last_SQL_Errno和Last_SQL_Error:SQL 线程最近一次报错。最常见的自然是Error_code: 1062主键冲突,其次是1032找不到更新或删除的行。Last_Error:综合展示最近一次错误的详细信息,很多排障的第一步就是把这一行贴到搜索引擎里。
2. 复制模式的取舍与二进制日志格式选型
2.1 异步、半同步、组复制,到底怎么选
MySQL 默认的复制模式是异步复制。所谓异步,指的是主库提交事务时,完全不管从库有没有收到 binlog,提交成功就立刻返回客户端。这种模式性能最好,主库完全不受从库影响,但风险在于:一旦主库在 binlog 还没传达到从库时宕机,强制切换后从库上就会缺一段数据,业务无法接受严格的数据零丢失场景。
半同步复制解决了这个“主库提交后、从库未收到”的窗口问题。原理是在主库事务提交的某个阶段,必须等待至少一个从库返回确认信息,确认它已经收到这个事务的 binlog,主库才向客户端返回成功。这个确认动作发生在事务真正提交之前,所以能在不牺牲太多性能的前提下,大幅降低数据丢失的概率。
组复制(Group Replication)则是更接近分布式共识的方案。它基于 Paxos 协议,在一组节点之间同步数据,任何一个节点提交事务,都需要组内超过半数的节点确认。这个方案能提供真正意义上的多节点强一致,但代价是配置复杂度陡然上升,网络要求也更严苛,适合对一致性要求极高、愿意付出性能代价的业务场景。
如果你是第一次搭建主从,我更推荐先跑标准异步复制,把机制搞熟,然后根据业务 RTO/RPO 需求再考虑升级到半同步。半同步的超时时间、退化策略这些参数,在线上环境里需要结合实际压测结果去调整,贸然开启有时反而会造成主库提交阻塞。
| 模式 | 数据安全性 | 性能影响 | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| 异步复制 | 可能丢数据 | 最小 | 低 | 日志、报表等容忍小窗口丢失的业务 |
| 半同步复制 | 至少一个从库已确认 | 中等 | 中 | 交易类核心业务,RPO 接近零 |
| 组复制 | 多数派确认,强一致 | 较大 | 高 | 金融级核心账务、分布式数据库建设 |
2.2 binlog 格式:statement、row、mixed 的利弊
binlog 是主从复制的“原语”,它以什么格式记录数据变更,直接影响从库回放的效果。
statement 格式记录的是原始 SQL 语句。比如一条UPDATE user SET age = age + 1 WHERE id > 100,在从库上重放时,主库和从库会各自执行这条语句。这种格式日志量小,网络开销低,但有一个致命问题:不确定函数或非确定性的操作可能导致主从数据不一致。比如UPDATE里用了NOW()、RAND()、UUID(),主从执行时取到的值可能不同,从库就会慢慢跑偏。
row 格式记录的不再是 SQL 语句,而是每一行数据变更前后的具体值。主库执行一条DELETE FROM user WHERE age < 18时,binlog 里记录的是被删掉的每一行的完整镜像。从库回放时,直接按主键定位并改动对应行就好了,不再受 SQL 语义影响。这也是为什么在 MySQL 5.7 之后,系统默认的 binlog 格式就是 row。
row 格式的代价是日志量明显变大。一个批量 UPDATE 影响一百万行,statement 格式可能就几条日志,row 格式会把这一百万行的前后镜像都写进去。好在这个问题可以通过binlog_row_image=minimal参数缓解,只记录被修改的列,不记录无关列,日志体量能缩下来不少。
mixed 格式是前两者的折中:MySQL 自己判断,如果 SQL 是确定性的、不会引起主从不一致的,就用 statement 记录,否则自动切换成 row。听上去两全其美,但判断逻辑本身存在一些边界情况,实践中反而会让排查 log 增加难度。我的建议是:除非你对 binlog 格式的内部判断机制非常熟悉,否则直接用 row 格式最省心,日志大点就大点,磁盘便宜,数据不一致的代价可比磁盘贵多了。
2.3 binlog 保留时间与清理机制
binlog 还有个容易被忽视的问题——保留时长。默认情况下,MySQL 会根据expire_logs_days(8.0 里更推荐binlog_expire_logs_seconds)自动清理过期 binlog。这个参数一旦设得太短,从库 IO 线程请求的 binlog 位置已经被主库清理掉,从库就只能报1236错误无法继续同步。
我曾经见过一个备份系统,因为主库的 binlog 只保留了两小时,某天备份任务异常,从库停了 3 小时,等恢复时主库日志已经被清掉,只得重新做全量备份再追日志,耽误了大量时间。这里的经验是:binlog 保留时间不能只按“磁盘够不够”来定,还得考虑复制链路的故障恢复时间。一般我会建议保留 24 小时以上,并配合异地备份和监控告警,确保任何一次意外的同步中断都能靠本地 binlog 续上。
3. 从零搭建一主一从复制集群的完整过程
3.1 版本选型和基本规划
动手之前先把版本和结构定好。这里以 MySQL 8.0 为主讲,因为 8.0 是当前长期支持版本的主力,也是新项目默认选择。搭建一主一从,需要规划三个基础信息:实例端口、server_id、数据目录。
server_id是整个复制拓扑里每个节点的唯一标识,整个集群里绝对不允许重复。它不要求和你业务里的任何编号对应,唯一的硬性要求就是唯一。有人图省事把主库 server_id 设为 1,从库设为 2,这个习惯在普通一主一从里没问题,但以后扩展复杂拓扑时容易撞车,建议一开始就按业务或机房维度分配一个段位。
端口规划也一样,别随手拿个 3306 就用。如果同一台机器上以后要跑多个实例,端口之间要有清晰的数字区间。我的习惯是主实例用 3306,从实例用 3307,以一种“一眼能看出角色”的方式来规划。
3.2 主库配置与复制账号创建
主库的 my.cnf 需要开启 binlog 并配置 server_id。以最小化配置为例:
[mysqld] server_id = 1 log_bin = mysql-bin binlog_format = row binlog_row_image = minimal sync_binlog = 1 expire_logs_days = 7log_bin = mysql-bin定义了 binlog 文件的基名,实际文件会生成mysql-bin.000001、mysql-bin.000002这样的递增文件。sync_binlog = 1表示每次事务提交后强制把 binlog 刷到磁盘,这会在一定程度上影响性能,但能保证主库在掉电时不把 binlog 弄丢。如果对性能特别敏感、又能容忍极端情况下的少量日志丢失,可以调成 0 或 N。
复制账号的创建要遵循最小权限原则。在 MySQL 8.0 里,一条语句即可实现:
CREATE USER 'repl'@'192.168.1.%' IDENTIFIED WITH mysql_native_password BY 'YourStrongPass'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.1.%'; FLUSH PRIVILEGES;注意账号主机段别写成%,尽量限定到网段。这个账号只拥有复制权限,绝对不能把它当成业务账号去使用。设置密码时,我建议强度拉满,因为复制账号在网络上传输的数据本身就是敏感的。
3.3 从库初始化:利用物理备份追赶主库数据
一主一从的搭建难点,往往不在“配复制”,而在“把主库已有的数据先搬到从库”。如果主库刚初始化,没有任何业务数据,可以直接跳过数据同步,从库配好复制参数就能开始。但生产环境基本都是带着数据在跑的,所以最常用的方式是用物理备份来初始化从库。
主流数据库备份工具(如 XtraBackup,或 8.0 自带的 CLONE 插件)都可以实现这个目标。以 CLONE 插件为例,MySQL 8.0 提供了内置的数据克隆能力,直接在从库上发起 CLONE 就能从主库拉取全量数据,效率很高,而且支持增量同步。CLONE 的细节较多,作为示例我这里更推荐传统但稳定的流程:主库做一次全量备份,把备份传输到从库,恢复后做 CHANGE MASTER,然后START SLAVE。
如果数据量不大,也可以先对主库执行一次全局锁读:
FLUSH TABLES WITH READ LOCK;然后打包主库的数据目录,传到从库,再释放锁。这个方式只适合数据量小、能接受短暂只读的场景。数据量上了几十 GB 之后,FLUSH TABLES WITH READ LOCK 的操作时间长,风险高,优先考虑专业备份工具。
3.4 从库配置与启动复制
从库的 my.cnf 加上:
[mysqld] server_id = 2 relay_log = mysql-relay-bin log_slave_updates = 1 read_only = 1log_slave_updates = 1是很多人在级联复制时才会想到的参数,它的含义是让从库在执行完 relay log 之后,也把变更写进自己的 binlog。如果以后要在这个从库下面再挂一个从库,这个参数就必须开启。哪怕现在不需要,我也建议一开始就打开,后面扩展拓扑时不用改配置重启。
read_only = 1是从库的“软只读”开关,普通账号无法写入,但复制线程和管理员账号可以写入。它并不能替代严谨的权限管理,但能在一定程度上阻止业务把脏数据写进从库。
接下来在从库上指定主库的连接信息:
CHANGE MASTER TO MASTER_HOST = '192.168.1.10', MASTER_PORT = 3306, MASTER_USER = 'repl', MASTER_PASSWORD = 'YourStrongPass', MASTER_LOG_FILE = 'mysql-bin.000013', MASTER_LOG_POS = 593;MASTER_LOG_FILE和MASTER_LOG_POS这两个值,来自你做备份时记录下来的 binlog 坐标。如果备份工具不支持自动记录坐标,你需要手动去主库执行SHOW MASTER STATUS拿到当前位置。
最后执行:
START SLAVE;然后立刻查看状态:
SHOW SLAVE STATUS\G看到Slave_IO_Running: Yes和Slave_SQL_Running: Yes,并且Seconds_Behind_Master逐渐归零,基本就算大功告成了。
注意:
START SLAVE之后不要急着直接查看Seconds_Behind_Master,它第一次显示可能是一个很大的值,因为 SQL 线程需要先追平已有的日志积压,这个数字会随着执行渐渐降下来。
3.5 安全性调优:主库提交参数和从库半同步
为了让复制在异常场景下更安全,主库还有几个参数值得重点配置:
innodb_flush_log_at_trx_commit = 1这个参数设置的是 InnoDB 的 redo log 刷盘策略。设为 1 意味着每个事务提交时都要把 redo log 刷到磁盘,配合sync_binlog = 1,可以保证主库在任何时刻宕机,已提交的事务都不会丢失。如果是异步复制,这里不加以约束的话,崩溃恢复后主从两边可能出现数据错位,排查起来极其痛苦。
半同步复制的开启方式,是在主从两边分别安装插件:
主库:
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; SET GLOBAL rpl_semi_sync_master_enabled = 1; SET GLOBAL rpl_semi_sync_master_timeout = 1000;从库:
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; SET GLOBAL rpl_semi_sync_slave_enabled = 1;然后重启从库的 IO 线程,让半同步配置生效。rpl_semi_sync_master_timeout = 1000表示如果等不到从库确认,主库等待 1 秒后会退化为异步模式,避免主库提交被彻底卡死。这个超时值没有万能答案,太短了半同步形同虚设,太长了主库响应会很慢,需要在业务允许的延迟范围内测试。
4. 故障复盘:主从延迟的根因和常见异常速查
4.1 从库延迟为什么这么顽固
延迟是主从架构里最经典的问题,没有之一。老版本 MySQL 的单线程 SQL 回放,一旦碰到主库并发写入高,哪怕从库硬件比主库好,也经常追不上。核心瓶颈在于:主库是多线程并发写入,从库只能单线程顺序执行,天然就有一道跨不过去的坎。
好在这几年并行复制一直在进化。从 5.6 的按库并行,到 5.7 的按事务并行,再到 8.0 基于 write set 的精细化并行,回放能力已经大幅提升。如果你还在用默认配置,建议检查一下这几个参数:
replica_parallel_workers = 4 replica_parallel_type = LOGICAL_CLOCK replica_preserve_commit_order = 1replica_parallel_workers决定 SQL 线程并行应用的线程数,取值要结合从库 CPU 核数来定。8 核以下的从库设 4 就够了,再多并不会线性提升,反而会增加锁竞争。replica_preserve_commit_order = 1保证并行事务在从库上的提交顺序和主库保持一致,这能规避很多隐性一致性问题。
延迟的另一个隐蔽原因是大事务。一条UPDATE语句影响几百万行,或者一条DELETE删全表,都会在主库 binlog 里形成巨大单条事件。SQL 线程在回放这个大事务时,其他所有事务都只能排队等它结束,延迟自然飙升。解决思路不在从库,而在应用侧:拆分大事务,控制单条 SQL 影响行数,从源头规避。
还有一类延迟是主从硬件差异造成的。从库机器如果比主库低一档,比如主库用的 NVMe SSD,从库却用普通机械盘,SQL 线程再怎么并行也填不平差距。生产环境里主从硬件实力尽量对齐,尤其是磁盘和内存这两个最关键的硬件指标。
4.2 主从复制的典型错误与处理方法
下面这些错误,是我在实际维护时遇到频率最高、也最有代表性的几类:
| 错误现象 | 错误码 | 常见原因 | 处理思路 |
|---|---|---|---|
Duplicate entry 'xxx' for key 'PRIMARY' | 1062 | 从库上存在主库已删除或修改过的记录,或手动往从库写过重复数据 | 先查从库数据是否被人工改过;若是复制本身数据不一致,可以用临时表辅助修正后 SET GLOBAL sql_slave_skip_counter = 1(仅限确认跳过安全时) |
Could not execute Delete_rows event | 1032 | 从库上找不到要被删的行,通常因历史数据不一致 | 核对主从数据,考虑从备份重新同步,或跳过该事务后做数据校验修复 |
Got fatal error 1236 | 1236 | IO 线程请求的 binlog 位置已经不存在(被清理)或坐标错误 | 确认主库 binlog 保留情况;若已丢失,需要重建从库或利用备份重新追平 |
Lost connection to MySQL server | 2013 | 网络抖动、主库停机、连接超时 | 检查网络稳定性、主库负载、max_allowed_packet 是否过小 |
The slave I/O thread stops because master and slave have equal MySQL server UUIDs | 1593 | 从库和主库的 server_uuid 一致,常见于克隆虚拟机或复制数据目录 | 删除从库 auto.cnf 文件并重启实例,生成新的 UUID |
处理 1062 这类复制中断错误时,要特别谨慎。跳过事务只能解决眼前报错,但会造成主从数据缺口。如果错误的产生是因为线上人为误操作,最稳妥的做法还是从备份重建从库,或者借助工具做行级数据校验和修复,而不是一味 skip。真正到了迫不得已要跳过时,sql_slave_skip_counter只跳过当前一个事务,不是让 SQL 线程无限跳过,用的时候心里要有数。
4.3 一次典型的延迟排查演练
为了把前面的知识串起来,我假设一个场景:某公司订单库主从复制拓扑,业务高峰期收到告警,从库Seconds_Behind_Master达到 300 秒,业务读写分离方案里,读请求开始大量超时。
排查第一步,SHOW SLAVE STATUS\G确认两个线程是正常的,Relay_Master_Log_File和Exec_Master_Log_Pos落后主库的 binlog 位置一大截。这说明 IO 线程不慢,数据都拉到 relay log 里了,瓶颈在 SQL 线程回放。
第二步,SHOW PROCESSLIST查看 SQL 线程的当前状态。这时候能看到它正卡在一条UPDATE orders SET status = ... WHERE create_time BETWEEN ...的语句上。这条语句在主库执行时可能只用了 2 秒,但在从库上因为某个字段没有索引,全表扫描导致 5 分钟还没跑完。
第三步,用EXPLAIN分析这条语句的执行计划,发现create_time上没有索引,补上一个联合索引后,从库回放速度立刻恢复正常。
这个案例的启示是:主从延迟不一定是“从库机器不行”,也不一定是“并行复制没开”,很多时候是主库一条慢 SQL 在从库上被放大。主库执行慢 SQL,只影响这一条会话;从库是单线程回放,一条慢 SQL 能把所有事务的同步进度全部堵住。所以主从架构里,对慢查询的容忍度要更加严格。
5. 架构演进:从单机复制走向高可用集群
5.1 GTID 复制:让故障切换不再依赖坐标
传统的CHANGE MASTER TO需要手动指定MASTER_LOG_FILE和MASTER_LOG_POS。一旦从库停了很久,或者拓扑里加了一台新节点,这个坐标找起来很容易出错。GTID(全局事务标识符)复制就是为了解决这个问题。
每个事务在主库执行时,都会被分配一个全局唯一的 ID,格式类似uuid:transaction_id。有了 GTID,从库不需要再关心 binlog 文件名和偏移量,只需要执行:
CHANGE MASTER TO MASTER_HOST = '192.168.1.10', MASTER_PORT = 3306, MASTER_USER = 'repl', MASTER_PASSWORD = 'YourStrongPass', MASTER_AUTO_POSITION = 1;从库会自动向主库请求自己缺失的 GTID 集合。这在主从切换场景下价值极大:原来需要精确记录每个旧主库的日志位置,现在只需要确保 GTID 集合连续,切换过程的人工操作量能少一大截。
开启 GTID 的方式,是主从都添加配置:
gtid_mode = ON enforce_gtid_consistency = ON5.2 读写分离架构下的从库使用规范
很多人搭主从的目的就是为了读写分离,但有几个隐蔽的坑必须提前提醒。
首先,从库上的数据可能不是实时最新的,哪怕只有几十毫秒。业务读写分离的场景里,如果刚写入的数据立刻从从库读,可能出现空洞。解决方式要么是让关键读请求强制走主库,要么是在应用层做“写后读”路由,或者引入短延迟容忍机制。
其次,从库不能随便开放写权限。read_only只能挡住普通账号,管不住 super 权限的账号。真正要做好读写分离,业务账号在从库上应该只有 SELECT 权限,从根上禁止写入行为,否则一个手滑把数据写进从库,后面追责和修复都极其痛苦。
最后,多个从库之间的负载均衡要尽量考虑数据分布。如果两台从库一台扛了 80% 的读流量,另一台闲得发慌,那慢查询和延迟大概率会集中出现在忙的那台机器上。接入中间件做读写分离时,要给流量配比做好监控和动态调整能力。
5.3 高可用方案演进:从 MHA 到组复制
复制解决的是“数据冗余”和“读扩展”,但一台主库宕机时,如何快速无损地切换流量,是高可用设计的重点。
传统方案里 MHA 是经典选择。它负责监控主库状态,在主库故障时自动从候选从库中选出数据最完整的一台,补齐日志后提升为新主库,并把其他从库重新指向新主库。MHA 工作得不错,但它的逻辑依赖外部脚本和管理节点,部署和运维成本不低,而且存在检测和切换之间的时间窗口。
MySQL 官方在 5.7.17 之后重点推的组复制方案,则把“选主”逻辑下沉到了数据库自身。组内的每个节点都参与事务共识,任何一个节点出问题,组内会自动选出新主库并继续服务,不需要外部切换脚本。配合 MySQL Router 这类中间件,可以实现应用层几乎无感知的故障转移。
组复制虽然听起来美好,但它对网络延迟和稳定性很敏感,节点之间要求非常低的网络抖动。跨机房部署时,如果机房之间专线质量不稳定,组复制的性能会受到明显拖累。所以方案选型还得回到业务一致性要求和容灾距离上来,核心交易场景可以考虑组复制,一般业务用主从复制配合自动切换脚本,已经能覆盖绝大多数需求。
5.4 复制链路监控与巡检建议
主从复制搭完不监控,等于裸奔。日常巡检建议至少关注以下几个指标:
Slave_IO_Running和Slave_SQL_Running是否都为 Yes。Seconds_Behind_Master的持续变化,而不是单次值。- 主从两边的 binlog 文件是否持续增长,磁盘剩余空间是否充足。
- 从库上的复制线程长时间运行时的系统负载、CPU 和磁盘 IO 是否正常。
- 半同步模式下,
Rpl_semi_sync_master_status是否为 ON,是否频繁退化为异步。
我个人的习惯是在巡检脚本里把SHOW SLAVE STATUS的关键字段解析成监控指标,丢到监控系统里,再配几个基础告警:复制中断立刻告警、延迟超过阈值(比如 30 秒)告警、半同步退化告警。告警阈值宁低勿高,因为复制中断这类问题,发现得越晚,修复成本越高。
结尾
写到这里,主从复制的核心链路已经走完一遍。我在实际配置和维护主从架构时,最大的感受是:复制本身并不复杂,难的是“知其所以然”。只要理解了 binlog 是数据搬运的载体、IO 线程和 SQL 线程的分工边界、半同步和异步之间的权衡,日常出问题时你至少知道该往哪个方向去查,而不是对着报错乱试。最后再分享一个小习惯:每次搭完一个新的复制拓扑,我都强制自己模拟一次从库宕机和恢复过程,亲手跑一遍“重新做全量备份、重新 CHANGE MASTER、重新追上主库”的流程。这个过程看起来麻烦,但真到了故障发生时,它能帮你省下至少一个通宵的抢救时间。