← 每日面试题 学习看板

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

面试题/未分类处理/2026/09/5亿订单分库分表与双写灰度迁移.md

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

首次归档:2026-09-15 R1 | 难度:进阶

来源文档:interview-2026-09-15.md


原题

场景:

某电商系统订单表 orders 单表 5 亿行,单库单表已无法支撑业务增长。查询 P99 延迟从 50ms 飙到 2s,DB CPU 长期 90%+,插入 TPS 3000/s 导致主从延迟 30s+。

问题:

  1. 如何设计分库分表方案?分片键选什么?为什么?
  2. 如何迁移 5 亿行历史数据?如何保证迁移期间不停机?
  3. 迁移完成后,如何验证数据一致性?如何回滚?

我的回答(2026-09-15)

首先第一个是分库分表意见。通过当前数据量跟数据表情况,订单表的话,正常查询的方式是以人、用户、时间维度。所以拆分的话,最好是以时间维度去作为分片键。用人和用用户维度的话,作为数分片键的话,查询很难通过一个单个查询去查询结果。而以人跟用商铺的查询,可以创建关联子表去冗余数据,然后去进行对应的查询跟统计。 然后对于历史数据的迁移,首先 5 亿历史数据都有有价值吗;这 5 亿数据都有必要迁移吗?正常来说,历史大量的历史数据应该作为冷数据处理,放到冷存储里边,没有必要的查询。正常的话查询也会是统计性的查询,不会查 5 亿所有的历史数据,这些数据可以放到,比如 ES 啊,或者是做归档数据啊,这种,然后做历史查询,而不是去整体,这样我们可迁移的数据应该是最近 3 个月的,或者是最近一段时间有效期的。

点评(2026-09-15)

优点:

不足:

得分:70/100

讲解

问题1:分库分表方案设计

分片键选择对比:

分片键优点缺点适用场景
user_id用户查询只路由 1 个分片热门用户数据倾斜订单
create_time数据均匀用户查询命中所有分片时序、日志
order_no订单号查询路由 1 个分片用户查询命中所有分片订单号场景

推荐:user_id hash 分片

跨分片查询解决:

查询类型解决方案
用户订单查询按 user_id 分片
时间范围查询同步到 ES/数仓
订单号查询建全局索引表
状态查询建状态路由表
-- 全局索引表(不分片)
CREATE TABLE order_index (
    order_no VARCHAR(64) PRIMARY KEY,
    user_id BIGINT NOT NULL,
    shard_table VARCHAR(32) NOT NULL
);

问题2:历史数据迁移方案

双写 + Canal 增量同步 + 灰度切流

阶段 1:准备(1 周)
阶段 2:双写(1-2 周)→ 灰度 1%
阶段 3:数据迁移(1 周)→ 灰度 10% → 50% → 100%
阶段 4:切换(1 天)→ 旧库只读
阶段 5:下线(1 周后)

Canal 监听 binlog:

@CanalInstance("orders_migration")
public class OrderCanalListener implements CanalMessageListener {
    public void onMessage(CanalEntry.Entry entry) {
        switch (eventType) {
            case INSERT: newOrderDao.insert(order); break;
            case UPDATE: newOrderDao.update(order); break;
            case DELETE: newOrderDao.delete(order); break;
        }
    }
}

问题3:数据验证 + 回滚

验证 3 步:

  1. 行数对比
  2. Checksum 校验(按分片)
  3. 抽样校验(1000 条)

回滚流程:

  1. 停止双写新库
  2. 确认旧库完整
  3. 暂停 Canal
  4. 流量全部走旧库
  5. 告警 + 记录

反向同步:新分片 → 旧表(Canal 反向监听)

关键认知:


面试记录 2026-09-23

题目:5 亿订单分库分表与双写灰度迁移

难度:high(重考)

标签:uncategorized, architecture, tech/database/index, tech/transaction, difficulty/medium

评分:⭐⭐⭐⭐⭐(5/5)

我的回答

5 亿订单的数据,其中需要确认一下这订单数据的存在时间,上下线,是短期内就有 5 亿,还是一个聚集了很多年的数据才有了 5 亿的数据。如果是很多年的数据,就将冷数据进行归档,只留保留近期活跃的数据以及当前状态为有效的数据,其他的数据都做归档,放到数仓或者是 ES、Clickhouse 这种中去冷存储。后续的数据用来搜索也好、查看也好、做报表也好,都直接从数仓里拿。然后热数据近几个月的数据可能就没有这么多了。如果按一天,每天百万数据量去算,一个月是 3000 万,三个月大概是 1 个亿,每天百万数据,5 个亿的数据也就是 15 个月。我们保留近 3 个月的数据,也就是 1 个亿的数据。1 个亿的数据做分库分表的话,可能 2000 万到 3000 万数据分一个表,也就是 5 张到 6 张表就够了。双写灰度迁移,这个操作历史数据直接就迁移过去了。然后新的数据在增长过程中同步去写,写到新库,然后做每日的数据,实时的数据同步,通过 binlog 也好,通过其他的模式也好,直接写到分表数据内。然后每天做数据校验,对 T 减一的数据每天做数据校验,确保数据同步的没有异常。然后将部分服务的读切到灰度的新的订单分表上,验证业务是否存在问题。如果没有问题,在执行把整个数据全部写量都完成之后。就可以清库了。

点评

优点:

不足(几乎无可挑):

完整讲解

第一部分:知识重点剖析

核心考点定位:能否给出端到端的分片方案 + 不停机迁移方案 + 数据一致性验证 + 回滚兜底。

知识点拆解:

  1. 分片方案设计:
  1. 不停机迁移五阶段:
  1. 一致性方案:
  1. 回滚方案(关键):
  1. 异构索引表(跨分片查询兜底):
  1. 冷热分层存储:

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. 双写期间如何保证主库和新分片数据一致?
  1. 如果灰度切流后发现问题,回滚怎么做?
  1. 5 亿行数据全量迁移要多久?如何加速?
  1. 分片规模选择:现在 5 张表够,为什么建议一开始就建 16 张?

第三部分:面试者参考回答(300-600 字口语化)

面试官您好,5 亿订单分库分表是一个端到端工程问题,我用五个阶段来拆解,重点是体量估算 + 冷热分离 + 灰度切流。

第一步:体量估算。5 亿数据要先搞清楚是不是堆积很多年的数据。假设日均 100w 单,5 亿 = 15 个月数据。热数据只留近 3 个月,也就是 1 亿数据进 MySQL 分片。按单表 2000w 行上限,规划 5-6 张表够用。历史 4 亿数据直接归档到 ES / ClickHouse,支持搜索和报表,不进 MySQL。这样真正需要迁移的分片数据只有 1 亿,风险骤降。

第二步:分片键选择。订单主查询是"我的订单",按 user_id 分片——单用户查询只路由 1 个分片。按 create_time 分片是错的:用户订单跨所有时间段,一次查询打所有分片。订单号查询、状态筛选用异构索引表兜底:order_index(order_no, user_id) 按 order_no 分片,status_index(status, order_id) 按 status 分片。

第三步:规模预留。虽然现在 5-6 张表够,但一开始就建 16 张表(2 的幂)。原因是 3-5 年后业务翻倍要扩容,扩容代价远高于预留成本。2 的幂扩容只迁 50% 数据,非 2 的幂扩容迁 80%+。

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

第五步:回滚方案。回滚的核心是流量回滚,不是数据回滚。反向 Canal 保持老库始终有数据,出问题 5 分钟内把流量切回老库即可。数据不迁回,等新分片问题修好再重新切流。

一致性保证:

冷/温/热三层存储:

一句话总结:先做体量估算和冷热分离把问题规模降到 1/5,再选 user_id 分片 + 异构索引表兜底,然后用 Canal 双写 + 灰度切流 + 反向 binlog 保证不停机 + 可回滚。核心认知是"迁移成本 = 数据规模 × 阶段数",能降规模就降规模,能并行就并行,能灰度就灰度。