主从、备份与扩展
本文性质声明:本机没有 MySQL/Postgres 实例,全部内容未实测,是文档级知识整理,用于建立复制与扩展的决策地图。要看实测数据,看同专栏 db-sqlite(SQLite 的
backup()与VACUUM INTO有本机测量)。任何数字都不要当成本书的结论——以你手头版本的官方文档为准。
一、复制链路:三个环节,三个延迟源
MySQL 主从的基本链路:
主库写 binlog → dump 线程推送 → 从库 IO 线程写 relay log → 从库 SQL 线程 apply
延迟(Seconds_Behind_Master)只是从库 SQL 线程Apply 位置与主库位置的差距估算,它撒谎的场景很多。三个真实的延迟源:
- 大事务:主库一个
UPDATE扫 500 万行跑 30 秒,从库 apply 同样要 30 秒——这期间从库整体卡住,Seconds_Behind_Master冲到 30。拆分大事务是降延迟的第一手段,比任何调参都管用。 - 从库单线程 apply(5.6 前)或并行 apply 的粒度问题:5.6+ 支持按库并行、5.7+ 支持按逻辑时钟(
slave_parallel_workers)并行,但同一行的事务仍必须保序——热点行的更新在主从上都是串行瓶颈,并行度帮不上。 - 网络与 IO 竞争:从库的 relay log 写入、apply 与从库自身的读流量抢 IO。大查询把从库 IO 打满时,apply 变慢,延迟雪崩。
半同步复制(semisync)是延迟与可靠性的折中:主库提交前等至少一个从库确认收到 binlog。它防的是”主库挂了、binlog 还没传到从库”的数据丢失场景,代价是每次提交多一次网络往返——跨机房部署时这个往返可能是几十毫秒,写 QPS 直接受制于它。
二、备份三分法:冷备、逻辑备、物理备
选备份方式前先回答三个问题:能停多久(RTO)、能丢多少(RPO)、备份期间服务是否可用。
| 方式 | 手段 | RTO | RPO | 停服 | 适用 |
|---|---|---|---|---|---|
| 冷备 | 停库 + cp 数据文件 | 分钟级 | 停服时刻 | 是 | 小库、维护窗口 |
| 逻辑备 | mysqldump / mydumper | 小时级(导回慢) | 导出时刻 | 否(--single-transaction) | 跨版本迁移、单表抽取 |
| 物理备 | Percona XtraBackup / MySQL Enterprise Backup | 分钟级 | 备份时刻 | 否(热备) | 生产标配 |
三个实践要点:
mysqldump --single-transaction用 InnoDB 的一致性快照,不加--master-data=2的话恢复出来的库没有位点信息,没法用它搭从库——这是逻辑备份最容易被忘的参数。- XtraBackup 类的物理备份必须配 binlog:备份截到某个位点,位点之后的变更在 binlog 里,恢复后要重放(
mysqlbinlog按位点/时间)才能到故障前一刻。只还原物理备份 = 丢一段时间。 - 备份要能恢复才算备份。见第三节的演练清单。
三、恢复演练:90% 的团队输在没演练
备份文件的完整性和恢复流程的可执行性是两回事。最小演练集(每季度跑一次,计时):
- 全量恢复:拿最近一次全备,在隔离环境还原,记录耗时 → 这是 RTO 的真实值,不是文档里写的。
- 增量恢复:全备 + binlog 重放到指定时间点 → 验证
mysqlbinlog --stop-datetime的用法和位点换算。 - 校验数据:
CHECKSUM TABLE或行数/汇总值对账 → 证明恢复出来的库是对的,不只是”能启动”。 - 灰度回切:恢复实例挂只读,跑一遍核心查询 → 证明应用能连上、慢查询没退化。
演练暴露的经典问题:备份脚本在备份机上跑了三年,从没在恢复机上试过(缺依赖、缺权限);binlog 被 expire_logs_days 清得比备份周期还短(想增量恢复时日志没了);恢复文档写在 wiki 里,执行人已经离职。
四、读写分离:边界比收益重要
读写分离把读流量分给从库,但引入三个必须承认的边界:
- 复制延迟内的读到旧数据。用户刚支付完跳订单页,从库还没 apply 到这条记录——“我明明付了”。解法按严格度排序:关键读强制走主库(支付结果、库存)、会话内绑定(同一用户的连续操作粘在同一实例)、等待位点(
MASTER_POS_WAIT(),强一致但牺牲读扩展性)。 - 从库不是只读的。复制线程在写、
ANALYZE/优化在跑、其他查询在竞争 IO。把”最重的那张报表”打到从库,可能把 apply 延迟打到几十秒——从库的读负载要有预算,不是垃圾场。 - 故障转移有脑窗。主库挂了,提升从库需要:确认旧主真死(防网络分区双写)、选数据最新的从库、切 VIP/DNS、应用重连。中间几十秒到几分钟,写不可用。ProxySQL / Orchestrator / MGR 能自动化,但自动化前先把手动流程演练熟。
五、分库分表:四个触发条件与替代方案
分片是不可逆的架构决定,动手前先确认其他路真走完了:
| 触发条件 | 先试这些 | 还不行才分片 |
|---|---|---|
| 单表过大(> 数千万行,索引层高、DDL 慢) | 归档历史数据到冷库、分区表(PARTITION BY RANGE,单机内透明) | 按 key 分库分表 |
| 写 QPS 打满单机 | 合并提交、异步写、换存储(计数类场景) | 按用户/租户分片 |
| 单库连接数打满 | 连接池治理、只读从库分流、ProxySQL 多路复用 | 按业务垂直拆分 |
| 数据量大到备份恢复超 RTO | 物理备份 + 增量重放、分级存储 | 分片后每片独立备份 |
分片引入的新问题清单(决定前逐条确认有答案):分布式事务(XA/ Sagas / 最终一致 + 对账)、跨分片 JOIN(禁止或中间件广播)、全局 ID(雪花号/号段,不能靠自增)、扩容时的数据迁移(一致性哈希减少迁移量)、运维面翻倍(每片都要监控备份)。垂直拆分(按业务分库)通常比水平拆分(按 key 分表)性价比高得多——先垂直,真到单业务写不动了再水平。
六、容量规划与归档
最后是把上面所有东西串起来的日常:
- 磁盘增长曲线:数据 + binlog + undo + 临时文件分项统计,按天画。binlog 和 undo 的突增往往比数据本身更早预警问题(长事务、大事务的信号)。
- 归档策略:热数据 90 天在线、温数据转压缩表/对象存储、冷数据只留聚合。归档要按时间范围可回滚(删可恢复的,别直接 DROP)。
- 大表 DDL 计划:加列/改类型用在线工具(
pt-online-schema-change/gh-ost)或原生ALGORITHM=INPLACE, LOCK=NONE,避开高峰,先演练再上线。SQLite 侧的 ALTER 代价矩阵见 db-ddl-dml 实测。 - 连接数预算:应用池子总量 + 从库数 + 运维通道 <
max_connections,留 20% 余量给故障时的排查通道。
七、未实测清单
- 主从延迟数字、半同步的往返代价、并行 apply 的加速比——无实例,未测。
- XtraBackup 的备份/恢复耗时、
mysqlbinlog重放速度——未部署。 - ProxySQL/Orchestrator/MGR 的行为——未部署。
- 分区表的查询计划——SQLite 侧无等价物,未测。