---
title: 5 亿订单分库分表 + 双写灰度迁移
type: system
domain: sharding
tags: [sharding, migration, dual-write, gray-release, canal, binlog, hot-cold-separation]
status: learning
created: 2026-09-23
last_reviewed: 2026-09-23
method: feynman
related_questions:
  - "5亿订单分库分表与双写灰度迁移"
related_knowledge:
  - ./sharding-fundamentals.md
  - ./sharding-algorithms-id.md
  - ./sharding-join-pagination.md
  - ../mysql/mysql-transaction-core.md
  - ../mysql/mvcc.md
anki_cards: 7
interview_rounds:
  - "2026-09-23-round-1-Q10"
  - "2026-09-15-round-1-Q-sharding-500m"
---

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

> 5 亿订单分库分表是**端到端工程问题**。核心不是背方案，是**体量估算 + 冷热分离 + 灰度切流 + 反向 binlog 兜底**。选对分片键 > 选对迁移方案 > 选对扩容算法。

## 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 | ✅ | 需异构兜底 |

**跨分片查询兜底**：
```sql
-- 异构索引表：按 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 关闭
```

#### 第五步：一致性保证

**三大工具**：
1. **T-1 全表对账**：每日跑一次全表行数 + checksum + 抽样 1w 条明细
2. **实时对账**：写后抽样 1% 订单异步校验（延迟 < 5 秒）
3. **反向 binlog**：Canal 监听新分片 → 老库，保持老库始终有数据

**一致性矩阵**：

| 一致性级别 | 场景 | 方案 |
|-----------|------|------|
| 强一致 | 主表 + 异构索引表 | 本地事务 |
| 最终一致 | 主库 → 新分片 | Canal 异步 |
| 弱一致 | 主库 → ES | CDC + 延迟容忍 |

#### 第六步：回滚策略

**核心原则**：**流量回滚**，不是数据回滚。

```
正常路径: 老库写入 → 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**
- 回滚：**流量回滚，不是数据回滚**

## 常见误区

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

## 延伸追问

1. **双写期间如何保证主库和新分片数据一致？**
   - 本地事务保证主表 + 异构索引表；Canal 异步同步到新分片；每日 T-1 全表对账；抽样实时对账；反向 Canal 兜底
2. **如果灰度切流后发现问题，回滚怎么做？**
   - 立即把流量切回老库（< 5 分钟）；反向 binlog 保证老库始终有数据；数据不迁回，等新分片修好再重新切流
3. **5 亿行数据全量迁移要多久？如何加速？**
   - Canal 全量抓 100w/分钟 → 5000 分钟 ≈ 80 小时；多机并行 + 多线程 INSERT 可压到 24 小时
4. **分片规模选择：现在 5 张表够，为什么建议一开始就建 16 张？**
   - 留 2 的幂扩容余量；后期扩容代价远高于预留成本；数据迁移是最高危操作
5. **分片键选 user_id 的理由？为什么不选 create_time 或 order_id？**
   - 主查询"我的订单"命中 user_id（90% 流量）；create_time 分片会导致用户订单跨所有分片；order_id 分片导致用户查询跨所有分片
6. **Canal 和 DTS 有什么差别？**
   - Canal 是阿里开源的 binlog 订阅工具；DTS 是云厂商的服务，功能更全但收费。自研场景用 Canal，云上业务用 DTS
7. **反向 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 的幂，避免以后扩容
```

## 关联题目（题库）

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

## 关联知识

- [分库分表基础概念 + 分片规模设计](./sharding-fundamentals.md)
- [分片算法 + 全局 ID 生成](./sharding-algorithms-id.md)
- [跨分片 JOIN 与分页方案](./sharding-join-pagination.md)
- [MySQL MVCC](../mysql/mvcc.md)
- [MySQL 事务核心机制](../mysql/mysql-transaction-core.md)
- [主题地图](./_moc.md)

## Anki 候选卡片

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

---

*最后更新：2026-09-23*
