5 亿订单分库分表 + 双写灰度迁移

sharding 📚 learning sharding-migration · sharding · migration · dual-write · gray-release · canal · binlog · hot-cold-separation

TL;DR(30 秒扫完)

  • 5 亿订单的真相:5 亿 ÷ 100w/天 = 15 个月数据;留 3 个月热 = 1 亿;1 亿 ÷ 2000w = 5-6 表;规划 16 张(2 的幂)
  • 冷热分离:热数据(3-6 个月)进 MySQL 分片;温数据(1-3 年)进 ES/ClickHouse;冷数据(>3 年)归档 OSS
  • 迁移五阶段:准备 → 全量迁移 → 双写 → 灰度切流 → 下线
  • 一致性三板斧:T-1 全表对账 + 抽样实时对账 + 反向 binlog
  • 回滚原则:流量回滚,不是数据回滚;反向 binlog 保持老库始终有数据

关键结论

结论 A迁移成本 = 数据规模 × 阶段数,能降规模就降规模
结论 B反向 binlog 是回滚兜底——保持老库始终有数据,出问题 5 分钟切回
结论 C一开始就建 16 张表(2 的幂),留翻倍扩容空间
结论 D分片键选 user_id,主查询命中,异构索引表兜底订单号/状态查询

完整讲解(费曼四步)

STEP 1 · 概念

5 亿订单分库分表是端到端工程:分片方案设计 + 历史数据迁移 + 不停机双写 + 灰度切流 + 数据一致性验证 + 回滚兜底。

STEP 2 · 大白话

搬家公司类比:
  • 5 亿件家具(订单)要从老房子(单库)搬到新大别墅(分片)
  • 老房子太大一次搬不完 → 冷热分离:常用的搬进新屋,不常用的存仓库
  • 搬家过程中老房子还要住人 → 双写:新动作两边都做
  • 慢慢把日常活动(读流量)挪到新屋 → 灰度切流
  • 万一搬错了 → 反向 binlog:新屋变更同步回老屋,随时能切回

STEP 3 · 底层

第一步:数据体量估算

假设日均 100w 单:
  • 5 亿 = 500 天 = 15 个月
  • 保留 3 个月热数据 = 1 亿行
  • 单表 2000w 上限 → 1 亿 ÷ 2000w = 5-6 张表
  • 规划 16 张表(2 的幂,留翻倍扩容空间)
为什么一开始建 16 张?
  • 3-5 年后业务翻倍要扩容
  • 8 → 16 只迁 50% 数据
  • 6 → 8 要迁 75%,代价大得多
  • 扩容代价远高于预留成本

第二步:分片键选择

为什么选 user_id?
方案主查询命中数据均匀不可变分片键
user_id✅ 我的订单(90%)✅ 雪花 ID✅推荐
create_time❌ 用户订单跨时段✅ 时序分布✅时序归档
order_id❌ 用户订单跨片✅ 雪花 ID✅需异构兜底
跨分片查询兜底:
-- 异构索引表:按 order_id 分片
order_index(order_id, user_id, status)

-- 异构索引表:按 status 分片
status_index(status, order_id, user_id)
订单号查询、状态筛选走异构表。

第三步:冷热分层存储

层时间范围存储用途
热3-6 个月MySQL 分片主业务查询
温1-3 年ES / ClickHouse搜索、报表
冷>3 年OSS 归档审计、按需恢复
真正需要迁移的分片数据只有 1 亿(vs 5 亿),风险骤降。

第四步:不停机迁移五阶段

阶段 1 · 准备(1 周)
├─ 新分片建表(16 张)
├─ Canal / DTS 部署
├─ 异构索引表建好
└─ 应用双写代码 ready

阶段 2 · 全量迁移(1 周)
├─ 1 亿热数据分批 ETL 到新分片
├─ 工具:Canal 全量抓 / gh-ost / DTS
├─ 100w/分钟 ≈ 80 小时(多机并行可压到 24h)
└─ 每日 T-1 全表对账(行数 + checksum)

阶段 3 · 双写(1-2 周)
├─ 应用层双写主库 + 新分片
├─ Canal 反向监听新分片兜底老库
├─ 本地事务保证主表 + 索引表一致性
└─ 实时对账:抽样 1% 订单异步校验

阶段 4 · 灰度切流(1 周)
├─ 读流量 1% → 10% → 50% → 100%
├─ 每档观察 24 小时业务指标
│   - 订单成功率
│   - P99 延迟
│   - 告警数
│   - 分片水位均衡度
└─ 出问题立即切回

阶段 5 · 切换 + 下线(1-2 周)
├─ 100% 读切后停老库写入
├─ 老库只读保留 7 天观察期
├─ 无异常下线
└─ 反向 binlog 关闭

第五步:一致性保证

三大工具:
  • T-1 全表对账:每日跑一次全表行数 + checksum + 抽样 1w 条明细
  • 实时对账:写后抽样 1% 订单异步校验(延迟 < 5 秒)
  • 反向 binlog:Canal 监听新分片 → 老库,保持老库始终有数据
一致性矩阵:
一致性级别场景方案
强一致主表 + 异构索引表本地事务
最终一致主库 → 新分片Canal 异步
弱一致主库 → ESCDC + 延迟容忍

第六步:回滚策略

核心原则:流量回滚,不是数据回滚。
正常路径: 老库写入 → Canal → 新分片同步
                    ↓
          灰度切流: 新分片承载读流量

出问题:
├─ 立即把读流量切回老库(< 5 分钟)
├─ 立即把写流量切回老库
├─ 反向 Canal 保持老库有数据(自动)
└─ 新分片问题修好再重新切流
为什么不做数据回滚?
  • 数据回滚成本高、风险大
  • 反向 binlog 已经保证老库有数据
  • 新分片问题通常是代码 bug 或数据脏,不是数据缺失

第七步:分片规模预留决策树

业务预估 3-5 年后目标规模
   ↓
   ├─ 单表 × N ≤ 1000w 行?
   │     ├─ 是 → 选 N = 2 的幂
   │     └─ 否 → 需要冷分离
   ↓
预估目标分片数 = ceil(目标规模 / 2000w)
   ↓
向上取到 2 的幂
   ↓
建表(即使当前用不到也建好,避免以后扩容)

STEP 4 · 简化

一句话总结:先估算降规模 → 选 user_id 分片 → 建 16 张表 → 五阶段迁移 → 反向 binlog 兜底。核心认知是"迁移成本 = 规模 × 阶段数"。 记忆口诀:
  • 分片键:主查询命中优先
  • 规模:留 2 的幂,避免以后扩容
  • 冷热:热分片,温 ES,冷 OSS
  • 迁移:准备 → 全量 → 双写 → 灰度 → 下线
  • 一致性:T-1 对账 + 实时抽样 + 反向 binlog
  • 回滚:流量回滚,不是数据回滚

常见误区

说"5 亿数据全部分片"
冷热分离降规模到 1/5,热数据才进分片
说"用 create_time 做分片键"
用户订单跨所有时间段,一次查询打所有分片,完全违背分片目的
说"回滚就是数据迁回"
回滚是流量切回,反向 binlog 已经保持老库有数据
说"迁移期间业务停摆"
不停机迁移是标准做法,Canal 双写 + 灰度切流实现
说"分片规模够用就行"
留 2 的幂是扩容成本控制,一次性到位比以后扩容便宜得多
说"双写用异步 MQ 就行"
主表 + 异构索引表必须本地事务保证一致;新分片同步可以异步 Canal

延伸追问

双写期间如何保证主库和新分片数据一致?
本地事务保证主表 + 异构索引表;Canal 异步同步到新分片;每日 T-1 全表对账;抽样实时对账;反向 Canal 兜底
如果灰度切流后发现问题,回滚怎么做?
立即把流量切回老库(< 5 分钟);反向 binlog 保证老库始终有数据;数据不迁回,等新分片修好再重新切流
5 亿行数据全量迁移要多久?如何加速?
Canal 全量抓 100w/分钟 → 5000 分钟 ≈ 80 小时;多机并行 + 多线程 INSERT 可压到 24 小时
分片规模选择:现在 5 张表够,为什么建议一开始就建 16 张?
留 2 的幂扩容余量;后期扩容代价远高于预留成本;数据迁移是最高危操作
分片键选 user_id 的理由?为什么不选 create_time 或 order_id?
主查询"我的订单"命中 user_id(90% 流量);create_time 分片会导致用户订单跨所有分片;order_id 分片导致用户查询跨所有分片
Canal 和 DTS 有什么差别?
Canal 是阿里开源的 binlog 订阅工具;DTS 是云厂商的服务,功能更全但收费。自研场景用 Canal,云上业务用 DTS
反向 binlog 有性能风险吗?
反向 binlog 是异步的,性能几乎无影响;主要风险是延迟(通常秒级),需要在业务侧接受这个延迟

速查表

数据估算:    5 亿 = 15 个月 → 留 3 个月 = 1 亿 → 建 16 张表(2 的幂)
分片键:      user_id(主查询命中)+ 异构索引表兜底
冷热分层:    热 3-6 月进 MySQL / 温 1-3 年进 ES / 冷 >3 年进 OSS
五阶段:      准备 → 全量迁移 → 双写 → 灰度切流 → 下线
一致性:      本地事务 + T-1 全表对账 + 抽样实时对账 + 反向 binlog
灰度切流:    1% → 10% → 50% → 100%,每档观察 24h
回滚:        流量回滚(< 5 分钟),不是数据回滚
预留:        留 2 的幂,避免以后扩容

Anki 候选卡片

Q: 5 亿订单分库分表的体量估算?
A: 5 亿 ÷ 100w/天 = 15 个月;留 3 个月热 = 1 亿;1 亿 ÷ 2000w = 5-6 表;规划 16 张(2 的幂)
Q: 为什么选 user_id 做分片键?
A: 主查询"我的订单"(90%)命中 user_id;create_time 分片会导致用户查询跨所有分片
Q: 5 亿订单冷热分层存储?
A: 热 3-6 月进 MySQL 分片 / 温 1-3 年进 ES/ClickHouse / 冷 >3 年进 OSS
Q: 不停机迁移五阶段?
A: 准备 → 全量迁移 → 双写 → 灰度切流 → 下线
Q: 迁移期间一致性三大工具?
A: T-1 全表对账(行数 + checksum + 抽样)+ 抽样实时对账 + 反向 binlog
Q: 回滚的核心原则?
A: 流量回滚,不是数据回滚;反向 binlog 保持老库始终有数据,5 分钟内切回
Q: 为什么一开始建 16 张表?
A: 留 2 的幂扩容余量;8→16 只迁 50% 数据;6→8 要迁 75%;扩容代价远高于预留成本

关联题目

  • ✅ 《5亿订单分库分表与双写灰度迁移》— 2026-09-23 Round 1, ⭐⭐⭐⭐⭐(最出彩一题,从 09-15 首次 70/100 显著进步到 5/5)
  • ⚠️ 《5亿订单分库分表与双写灰度迁移》— 2026-09-15 Round 1, 70/100(首次答错:分片键选时间维度是错的)

关联知识

迁移成本 = 数据规模 × 阶段数,能降规模就降规模,反向 binlog 兜底
搬家公司:老房子=单库,新别墅=分片。搬家期间老房子还要住人 → 双写+灰度切流;万一搬错了 → 反向 binlog 保持老屋有数据,随时切回
✦ 记 忆 口 诀 ✦
估算降规模 / user_id 分片 / 建 16 张表 / 五阶段 / 反向 binlog 流量回滚
关键可视化
5 亿订单体量估算与降规模
flowchart TB
  A[5 亿订单 15 个月数据] --> B[冷 热分离]
  B --> H[热数据 近 3 个月 1 亿行]
  B --> W[温数据 1 到 3 年 ES 或 ClickHouse]
  B --> C[冷数据 大于 3 年 OSS 归档]
  H --> D[分片真目标 1 亿行]
  D --> E[单表 2000w 上限]
  E --> F[需要 5 到 6 张表]
  F --> G[向上取 2 的幂 16 张]
  G --> G2[留翻倍扩容空间]
分片键决策树
flowchart TB
  A[订单分片键选择] --> B[主查询命中优先]
  B --> C[我的订单 90 主查询]
  C --> D[user_id 分片 推荐]
  B --> E[反例 create_time]
  E --> F[用户订单跨所有分片]
  B --> G[反例 order_id]
  G --> H[用户查询跨所有分片]
  D --> I[订单号 状态查询 走异构索引表兜底]
不停机迁移五阶段
flowchart LR
  S1[阶段 1 准备 1 周] --> S2[阶段 2 全量迁移 1 周]
  S2 --> S3[阶段 3 双写 1 到 2 周]
  S3 --> S4[阶段 4 灰度切流 1 周]
  S4 --> S5[阶段 5 切换 加 下线 1 到 2 周]
  S1 --> A1[新分片建表 16 张]
  S1 --> A2[Canal 加 DTS 部署]
  S1 --> A3[异构索引表建好]
  S2 --> A4[1 亿热数据 ETL]
  S2 --> A5[每日 T 减 1 全表对账]
  S3 --> A6[应用层双写 主库 加 新分片]
  S3 --> A7[Canal 反向监听兜底老库]
  S4 --> A8[读流量 1 到 10 到 50 到 100 百分号]
  S4 --> A9[每档观察 24 小时]
  S5 --> A10[老库只读 7 天观察期]
  S5 --> A11[无异常下线 反向 binlog 关闭]
迁移期一致性三大工具
flowchart TB
  C[一致性保证] --> A[本地事务]
  A --> A1[主表 加 异构索引表]
  A --> A2[写路径强一致]
  C --> B[T 减 1 全表对账]
  B --> B1[每日跑一次]
  B --> B2[行数 加 checksum 加 抽样 1w]
  C --> C1[抽样实时对账]
  C1 --> C2[写后抽样 1 百分号]
  C1 --> C3[异步校验 延迟小于 5 秒]
  C --> D[反向 binlog]
  D --> D1[Canal 监听新分片 到 老库]
  D --> D2[保持老库始终有数据]
  D --> D3[回滚兜底]
回滚流程:流量回滚 而非 数据回滚
flowchart TB
  N[正常路径] --> P1[老库写入]
  P1 --> P2[Canal 同步新分片]
  P2 --> P3[反向 Canal 老库兜底]
  P3 --> P4[灰度切流 新分片承载读]
  P4 --> B[出问题]
  B --> R1[立即切读流量回老库 5 分钟内]
  B --> R2[立即切写流量回老库]
  B --> R3[反向 binlog 保持老库有数据 自动]
  B --> R4[新分片问题修好再重新切流]
  R4 --> X[不做数据回滚]
  X --> X1[成本 高风险大]
  X --> X2[反向 binlog 已保证老库有数据]
  X --> X3[新分片问题通常代码 bug 而非数据缺失]
分片规模预留决策树
flowchart TB
  A[业务预估 3 到 5 年后目标规模] --> B[目标规模 除以 2000w 得到分片数]
  B --> C{是 2 的幂?}
  C -->|是| D[直接用]
  C -->|否| E[向上取 2 的幂]
  E --> F[建表 即使当前用不到]
  F --> G[避免以后扩容]
  G --> H[8 到 16 只迁 50 百分号 数据]
  G --> I[6 到 8 要迁 75 百分号 数据]
知识关系

🔄 延伸(Extends)

暂无

⚡ 对比(Contrast)

MySQL 事务核心— 分片迁移依赖本地事务保证主表+异构索引表一致性;跨分片用最终一致
🎯 概念 📏 规则 ⚠️ 误区 🔍 追问 ✨ 口诀 共 0 张卡,点击翻面