夜雨聆风学习资料网

ARTICLE · 1350827

Day70|MySQL 分库分表面试题:中间件怎么选?平滑迁移和分片路由全讲透

Day70|MySQL 分库分表面试题:中间件怎么选?平滑迁移和分片路由全讲透

Day70|MySQL 分库分表面试题:中间件怎么选?平滑迁移和分片路由全讲透

系列导语:这是一套「从入门到精通」的 Java 面试系列,按先基础后高级、先单点后综合的节奏日更。每天 3–5 道核心题,10–15 分钟读完就能消化吸收。

本文关键词:MySQL 分库分表、分库分表中间件、ShardingSphere、MySQL 平滑迁移、MySQL 分片路由

今日学习目标

今天进入架构层:分库分表。这是"重型武器",也是中高级面试的必考题。今天 5 道题覆盖选型、迁移、拆分策略、路由方式——答出"什么时候不该分库分表",比答出怎么分更有价值。建议先自己口述一遍,再对照解析查漏补缺。


题目1:常用的分库分表中间件有哪些?

【题目】:常用的分库分表中间件有哪些?怎么选型?

【考察意图】:考察你对主流方案的了解,以及是否能根据场景做取舍。

【标准回答】:

中间件
类型
现状与特点
适用场景
Apache ShardingSphereJDBC 直连
 + Proxy 代理 双模式
Apache 顶级项目
(2020 年毕业),社区最活跃,事实标准
绝大多数 Java 项目的首选
MyCat
数据库代理
早期流行,1.6 版本后社区活跃度大幅下降,MyCat2 生态远不如 ShardingSphere
老系统维护
TDDL
JDBC 直连
阿里内部演进而来,未正式开源,强依赖阿里生态
阿里系内部
Vitess
云原生代理
YouTube 开源,CNCF 毕业项目,为 K8s 设计,自带水平扩缩容
大规模云原生、多语言团队
PolarDB-X / TDSQL / GaussDB
云原生分布式数据库
云厂商托管,兼容 MySQL 协议,运维成本最低
上云且不想自建中间件

ShardingSphere 的两种模式(选型关键)

ShardingSphere-JDBCShardingSphere-Proxy
形态
Jar 包
,嵌入应用
独立进程
,部署为数据库服务
接入方式
应用直连数据库,无中间层
应用连 Proxy,Proxy 连数据库
性能
无网络跳转,损耗最小
多一跳网络,有损耗
语言支持
仅 Java任意语言
(MySQL 协议)
升级
要改应用依赖并重启
只升级 Proxy,应用无感知
运维
分散在各应用,难集中管控
集中配置、统一管控
ShardingSphere-JDBC:
  应用 ──(嵌入 Jar)──> 数据库集群

ShardingSphere-Proxy:
  应用 ──> Proxy ──> 数据库集群

选型结论:

  • • Java 单体 / 微服务,追求性能 → ShardingSphere-JDBC
  • • 多语言团队、需要集中管控 → ShardingSphere-Proxy
  • • 不想自建运维 → 云厂商分布式数据库(PolarDB-X / TDSQL)
  • • 已有 MyCat → 新项目别再引入,老系统逐步迁移

【加分项 / 避坑指南】:

  • • ✅ 加分:ShardingSphere 早已不只是"分库分表",已发展成生态圈:数据分片、读写分离、分布式事务(XA / Seata)、数据加密、影子库压测、数据脱敏、分布式治理。
  • • ✅ 加分:"先不要分库分表"才是高手答案。能说出分库分表前的替代手段清单非常加分:
    1. 1. 索引优化 + SQL 改写(成本最低,收益最高)
    2. 2. 加缓存(Redis 挡读流量)
    3. 3. 读写分离(读压力大时)
    4. 4. 硬件升级 / 参数调优(innodb_buffer_pool_size 等)
    5. 5. 历史数据归档(单表 5000 万里可能 4000 万是冷数据,归档后根本不用分)
    6. 6. 分区表(MySQL 原生 PARTITION,单表逻辑上还是一张表,业务改动小)
      → 这些做完还扛不住,才考虑分库分表。
  • • ⚠️ 避坑:分库分表不是银弹。引入后立刻会面对:SQL 兼容性受限(跨分片 JOIN、子查询、聚合有限制)、分布式事务、全局 ID、扩容数据迁移、运维复杂度翻倍。能用归档和缓存解决的,别分。
  • • ⚠️ 避坑:MyCat 现在不建议新项目引入。它曾是第一代方案,但社区已基本停滞,遇到坑很难找到维护者。

题目2:分库分表后,如何平滑迁移数据?

【题目】:分库分表后,如何平滑迁移数据(不停机)?

【考察意图】:考察你对数据迁移工程实践的理解,重点看能否保证业务不中断 + 可回滚。

【标准回答】:

核心方法论:双写 + 全量 + 增量 + 校验 + 灰度切换。分四个阶段:

阶段一:双写 + 全量迁移(读旧库)

  1. 1. 建好新库表结构(按新分片规则)
  2. 2. 改造数据访问层:写操作同时写旧库和新库(新库按新路由规则写入)
  3. 3. 全量迁移:用工具(DataX / 自研程序)把旧库历史数据搬到新库
  4. 4. 增量同步:订阅 binlog(如 Canal)把迁移期间的新变更补到新库
  5. 5. 数据校验:全量或抽样对比,补齐差异
应用 → 写:旧库 + 新库
     → 读:旧库
     Canal 订阅旧库 binlog → 补齐新库增量

阶段二:双写 + 读新库(灰度)

  1. 1. 确认数据一致性和新库性能 OK
  2. 2. 灰度切读:先按 1% → 10% → 50% → 100% 的比例逐步把读流量切到新库
  3. 3. 每档观察:慢 SQL、错误率、数据一致性
  4. 4. 继续双写(关键!这是回滚的保障)

阶段三:停写旧库

  1. 1. 确认 100% 读走新库且稳定
  2. 2. 停止写旧库,只写新库
  3. 3. 观察一段时间

阶段四:旧库下线

  1. 1. 确认没有任何业务请求依赖旧库
  2. 2. 旧库先"只读保留 1~2 周",确认无问题后再彻底下线

【加分项 / 避坑指南】:

  • • ✅ 加分(这题的题眼):双写期间新库写失败怎么办?
    • • 不能因为新库失败就让整个请求失败(新库异常不该影响线上)
    • • 标准做法:新库写失败只记日志 / 告警,不阻断主流程,同时靠 binlog 增量同步和定时对账补齐
    • • 或者写补偿队列,异步重试
  • • ✅ 加分:数据校验怎么做:
    • • 抽样对比:随机抽 N 条,逐字段比对(快、覆盖有限)
    • • 全量 checksum:对分片数据做 COUNT(*) + 关键字段 SUM() 校验
    • • 工具:DataX(全量)、Canal(增量)、自研对比程序
    • • 一定要对"行数 + 内容"双重校验,只校验行数会发现不了字段错位
  • • ⚠️ 避坑(最重要):必须保留回滚能力。任何时候都要能"一键切回旧库"——这就是双写必须持续到最后一刻的原因。不要急着删旧库。
  • • ⚠️ 避坑:别一次性全量切换。灰度切流 + 可快速回滚,是这类操作唯一的保险绳。

题目3:你们一般是怎么分库的?

【题目】:分库是怎么分的?

【考察意图】:考察分库的基本策略和适用场景。

【标准回答】:

分库分两种,通常先垂直、后水平。

① 垂直分库(按业务拆分)

把不同业务模块的表放到不同数据库。

拆分前:
  单库:用户表、订单表、商品表、日志表

拆分后:
  用户库:用户表
  订单库:订单表
  商品库:商品表
  日志库:日志表

优点:业务清晰、便于团队按模块自治维护、降低单库压力
缺点:跨库 JOIN 变难,需要业务层聚合(往往演变成跨服务调用)

② 水平分库(按数据拆分)

把同一个业务的数据按规则分散到多个库。

拆分前:
  订单库:所有订单

拆分后:
  订单库 0:user_id % 4 == 0 的订单
  订单库 1:user_id % 4 == 1 的订单
  订单库 2:user_id % 4 == 2 的订单
  订单库 3:user_id % 4 == 3 的订单

优点:解决单库数据量和并发/连接数瓶颈
缺点:分布式事务、全局 ID、跨库查询复杂度上升

什么时候该分库?

信号
经验阈值
单表数据量
2000 万 ~ 5000 万
以上(视行大小,B+ 树超过 3~4 层)
单库 QPS
逼近单机上限(一般几千)
连接数
经常打满 max_connections
磁盘
单库容量接近磁盘上限

【加分项 / 避坑指南】:

  • • ✅ 加分:分库分表的顺序是"先垂直后水平",而每一步之前先做轻量优化(索引、缓存、读写分离、归档)。直接上水平分库是过度设计。
  • • ✅ 加分:垂直分库往往会演进成微服务拆分——一个业务库对应一个服务,库边界就是服务边界。这体现架构视野。
  • • ⚠️ 避坑:垂直分库后,原本一条 JOIN 就能搞定的查询可能变成跨服务调用。设计时要把"哪些数据必须放一起"想清楚,避免拆完发现天天要跨服务聚合。

题目4:那你们是怎么分表的?

【题目】:分表是怎么分的?

【考察意图】:考察分表策略与适用场景。

【标准回答】:

分表也分垂直和水平。

① 垂直分表(按字段拆分)

把热点字段和不常访问的大字段拆到不同表。

拆分前:user(id, name, phone, avatar, profile, create_time)

拆分后:
  user_base:id, name, phone, create_time        ← 窄表,热点字段
  user_ext :id, avatar, profile                  ← 大字段(TEXT)

优点:减少单表宽度 → 一页能放更多行 → Buffer Pool 缓存命中率提升;大字段不参与热点查询
缺点:查全量数据需要 JOIN

② 水平分表(按数据拆分)

把同一表的数据按规则分散到多个表(order_0 ~ order_3)。

③ 两种拆分对比

垂直分表
水平分表
拆分依据
字段
(列)
数据行
解决什么
表太宽、大字段拖性能
行数太多、B+ 树太深
单表行数
不变
减少

【加分项 / 避坑指南】:

  • • ✅ 加分:垂直分表的底层收益——InnoDB 一页 16 KB,行越窄一页放的行越多,相同 Buffer Pool 能缓存更多有效行,索引树的扇出也更大。所以把 TEXT/BLOB 拆出去,对性能的影响是数量级的,不是"一点点"。
  • • ✅ 加分:水平分表后不能用数据库自增主键——多张表各自自增会撞号。必须用全局唯一 ID(雪花算法、号段模式等,Day71 详讲)。
  • • ⚠️ 避坑:分表数量要选合适的基数,且最好选 2 的幂(2/4/8/16)。原因是扩容时可以用"翻倍扩容",只需迁移一半数据(见题目5)。
  • • ⚠️ 避坑:垂直分表后别频繁 JOIN 两张表。如果确实需要同时查,考虑在查询侧做缓存,或者干脆接受冗余。

题目5:水平分表有哪几种路由方式?

【题目】:水平分表有哪几种路由方式?各自优缺点?

【考察意图】:考察路由策略的细节和选型依据。

【标准回答】:

① Hash 取模路由

inttableIndex= hash(userId) % 4;   // 路由到 user_0 ~ user_3

优点:数据分布均匀,不容易出现热点
缺点:扩容麻烦——从 4 张扩到 5 张,几乎所有数据都要重新分布

② 范围路由

按有序字段(时间、自增 ID)范围分片。

order_202401:2024 年 1 月订单
order_202402:2024 年 2 月订单
order_202403:2024 年 3 月订单

优点:扩容极其方便(加新表即可);适合按时间归档和冷热分离
缺点:容易产生热点(最新那张表访问量最大,比如"本月订单")

③ 一致性 Hash 路由

在 Hash 基础上引入虚拟节点,节点增减时只需迁移环上相邻的一小部分数据。

传统 Hash:  4 个节点 → 5 个节点,几乎全部数据要迁移
一致性 Hash:4 个节点 → 5 个节点,只迁移约 1/5 的数据

优点:扩容迁移成本低
缺点:实现和理解成本较高;数据分布不如取模均匀(靠虚拟节点缓解)

④ 更实用的方案:翻倍扩容(重点补充)

生产上比一致性 Hash 更常用的是翻倍扩容:

2 张表 → 4 张表 → 8 张表 → 16 张表

从 user_0/1 扩到 user_0/1/2/3:
  只需把 user_0 中 id % 4 == 2 的行迁到 user_2
     把 user_1 中 id % 4 == 3 的行迁到 user_3
  → 只迁移一半数据,规则简单,不易出错

这也是为什么分表数建议取 2 的幂。

⑤ 分片键怎么选(关键)

分片键(Sharding Key)决定了绝大多数查询能否"单分片命中",是分库分表成败的核心。

原则
说明
选查询最频繁的字段
如用户系统的 user_id、订单系统的 order_id
不要选会变的字段
比如 status、city 这类会被 UPDATE 的字段,改了就得跨分片搬数据
考虑"基因法"
让 order_id 的末几位带上 user_id 的分片信息,这样"按用户查订单"和"按订单查"都能单分片命中
非分片键查询怎么办
建异构索引表,或把数据异构到 ES,通过 ES 反查主键再精确查库

【加分项 / 避坑指南】:

  • • ✅ 加分:结合实际业务选型——
    • • 日志、流水、订单:按时间范围分片(天然归档,冷热分离)
    • • 用户、账户、商品:按 ID Hash 分片(访问均匀、无明显热点)
    • • 多租户 SaaS:按 租户 ID 分片(天然隔离)
  • • ✅ 加分:基因法是解决"多维度查询"的经典方案。比如 order_id 生成时,把 user_id % 16 的结果嵌入 order_id 的低位,那么按 order_id % 16 和按 user_id % 16 分片的结果一致,一次查询就能同时满足两种维度。
  • • ⚠️ 避坑:分片键选错 = 灾难。选错之后,业务查询要扫所有分片再在内存聚合(广播查询),性能可能比不分片还差,而且改分片键等于重新分库分表。设计阶段必须把主要查询路径枚举一遍。
  • • ⚠️ 避坑:跨分片分页/排序/聚合是伪需求。中间件做的方式是"每个分片各自排序 → 汇总 → 再排序",深分页时内存和耗时都会爆炸。业务上要限制翻页深度,或走 ES。

今日小结

今天我们梳理了 分库分表 的 5 个核心考点:

  1. 1. 中间件选型——ShardingSphere 是事实标准(JDBC 高性能 / Proxy 多语言);MyCat 不建议新项目引入;云上优先用托管方案
  2. 2. 平滑迁移——双写 + 全量 + 增量(Canal)+ 校验 + 灰度切流;新库写失败不能阻断主流程;旧库必须保留可回滚
  3. 3. 分库——先垂直(按业务)后水平(按数据);先做索引/缓存/读写分离/归档再考虑分
  4. 4. 分表——垂直分表(拆大字段,提升缓存命中);水平分表(分表数取 2 的幂,全局 ID 替代自增)
  5. 5. 路由——Hash 取模 / 范围 / 一致性 Hash,外加更实用的翻倍扩容;分片键选型决定成败

记住一条架构主线:分库分表是最后手段。先把索引、缓存、读写分离、历史归档做足,再谈分片。 💪

📌 系列导航:本文为《Java 面试通关指南》第 70 天 / 共 115 天。

明天预告:不停机扩容、百万数据怎么删、读写分离实现、主从延迟怎么处理、分库分表带来的问题——把分库分表的"坑"一次说清。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料