跳到正文

主从、备份与扩展

本文性质声明:本机没有 MySQL/Postgres 实例,全部内容未实测,是文档级知识整理,用于建立复制与扩展的决策地图。要看实测数据,看同专栏 db-sqlite(SQLite 的 backup() 与 VACUUM INTO 有本机测量)。任何数字都不要当成本书的结论——以你手头版本的官方文档为准。

一、复制链路:三个环节,三个延迟源

MySQL 主从的基本链路:

主库写 binlog → dump 线程推送 → 从库 IO 线程写 relay log → 从库 SQL 线程 apply

延迟(Seconds_Behind_Master)只是从库 SQL 线程Apply 位置与主库位置的差距估算,它撒谎的场景很多。三个真实的延迟源:

  1. 大事务:主库一个 UPDATE 扫 500 万行跑 30 秒,从库 apply 同样要 30 秒——这期间从库整体卡住,Seconds_Behind_Master 冲到 30。拆分大事务是降延迟的第一手段,比任何调参都管用。
  2. 从库单线程 apply(5.6 前)或并行 apply 的粒度问题:5.6+ 支持按库并行、5.7+ 支持按逻辑时钟(slave_parallel_workers)并行,但同一行的事务仍必须保序——热点行的更新在主从上都是串行瓶颈,并行度帮不上。
  3. 网络与 IO 竞争:从库的 relay log 写入、apply 与从库自身的读流量抢 IO。大查询把从库 IO 打满时,apply 变慢,延迟雪崩。

半同步复制(semisync)是延迟与可靠性的折中:主库提交前等至少一个从库确认收到 binlog。它防的是”主库挂了、binlog 还没传到从库”的数据丢失场景,代价是每次提交多一次网络往返——跨机房部署时这个往返可能是几十毫秒,写 QPS 直接受制于它。

二、备份三分法:冷备、逻辑备、物理备

选备份方式前先回答三个问题:能停多久(RTO)、能丢多少(RPO)、备份期间服务是否可用。

方式手段RTORPO停服适用
冷备停库 + cp 数据文件分钟级停服时刻是小库、维护窗口
逻辑备mysqldump / mydumper小时级(导回慢)导出时刻否(--single-transaction)跨版本迁移、单表抽取
物理备Percona XtraBackup / MySQL Enterprise Backup分钟级备份时刻否(热备)生产标配

三个实践要点:

  1. mysqldump --single-transaction 用 InnoDB 的一致性快照,不加 --master-data=2 的话恢复出来的库没有位点信息,没法用它搭从库——这是逻辑备份最容易被忘的参数。
  2. XtraBackup 类的物理备份必须配 binlog:备份截到某个位点,位点之后的变更在 binlog 里,恢复后要重放(mysqlbinlog 按位点/时间)才能到故障前一刻。只还原物理备份 = 丢一段时间。
  3. 备份要能恢复才算备份。见第三节的演练清单。

三、恢复演练:90% 的团队输在没演练

备份文件的完整性和恢复流程的可执行性是两回事。最小演练集(每季度跑一次,计时):

  1. 全量恢复:拿最近一次全备,在隔离环境还原,记录耗时 → 这是 RTO 的真实值,不是文档里写的。
  2. 增量恢复:全备 + binlog 重放到指定时间点 → 验证 mysqlbinlog --stop-datetime 的用法和位点换算。
  3. 校验数据:CHECKSUM TABLE 或行数/汇总值对账 → 证明恢复出来的库是对的,不只是”能启动”。
  4. 灰度回切:恢复实例挂只读,跑一遍核心查询 → 证明应用能连上、慢查询没退化。

演练暴露的经典问题:备份脚本在备份机上跑了三年,从没在恢复机上试过(缺依赖、缺权限);binlog 被 expire_logs_days 清得比备份周期还短(想增量恢复时日志没了);恢复文档写在 wiki 里,执行人已经离职。

四、读写分离:边界比收益重要

读写分离把读流量分给从库,但引入三个必须承认的边界:

  1. 复制延迟内的读到旧数据。用户刚支付完跳订单页,从库还没 apply 到这条记录——“我明明付了”。解法按严格度排序:关键读强制走主库(支付结果、库存)、会话内绑定(同一用户的连续操作粘在同一实例)、等待位点(MASTER_POS_WAIT(),强一致但牺牲读扩展性)。
  2. 从库不是只读的。复制线程在写、ANALYZE/优化在跑、其他查询在竞争 IO。把”最重的那张报表”打到从库,可能把 apply 延迟打到几十秒——从库的读负载要有预算,不是垃圾场。
  3. 故障转移有脑窗。主库挂了,提升从库需要:确认旧主真死(防网络分区双写)、选数据最新的从库、切 VIP/DNS、应用重连。中间几十秒到几分钟,写不可用。ProxySQL / Orchestrator / MGR 能自动化,但自动化前先把手动流程演练熟。

五、分库分表:四个触发条件与替代方案

分片是不可逆的架构决定,动手前先确认其他路真走完了:

触发条件先试这些还不行才分片
单表过大(> 数千万行,索引层高、DDL 慢)归档历史数据到冷库、分区表(PARTITION BY RANGE,单机内透明)按 key 分库分表
写 QPS 打满单机合并提交、异步写、换存储(计数类场景)按用户/租户分片
单库连接数打满连接池治理、只读从库分流、ProxySQL 多路复用按业务垂直拆分
数据量大到备份恢复超 RTO物理备份 + 增量重放、分级存储分片后每片独立备份

分片引入的新问题清单(决定前逐条确认有答案):分布式事务(XA/ Sagas / 最终一致 + 对账)、跨分片 JOIN(禁止或中间件广播)、全局 ID(雪花号/号段,不能靠自增)、扩容时的数据迁移(一致性哈希减少迁移量)、运维面翻倍(每片都要监控备份)。垂直拆分(按业务分库)通常比水平拆分(按 key 分表)性价比高得多——先垂直,真到单业务写不动了再水平。

六、容量规划与归档

最后是把上面所有东西串起来的日常:

  1. 磁盘增长曲线:数据 + binlog + undo + 临时文件分项统计,按天画。binlog 和 undo 的突增往往比数据本身更早预警问题(长事务、大事务的信号)。
  2. 归档策略:热数据 90 天在线、温数据转压缩表/对象存储、冷数据只留聚合。归档要按时间范围可回滚(删可恢复的,别直接 DROP)。
  3. 大表 DDL 计划:加列/改类型用在线工具(pt-online-schema-change / gh-ost)或原生 ALGORITHM=INPLACE, LOCK=NONE,避开高峰,先演练再上线。SQLite 侧的 ALTER 代价矩阵见 db-ddl-dml 实测。
  4. 连接数预算:应用池子总量 + 从库数 + 运维通道 < max_connections,留 20% 余量给故障时的排查通道。

七、未实测清单

  • 主从延迟数字、半同步的往返代价、并行 apply 的加速比——无实例,未测。
  • XtraBackup 的备份/恢复耗时、mysqlbinlog 重放速度——未部署。
  • ProxySQL/Orchestrator/MGR 的行为——未部署。
  • 分区表的查询计划——SQLite 侧无等价物,未测。