ARTICLE · 1350827
Day70|MySQL 分库分表面试题:中间件怎么选?平滑迁移和分片路由全讲透
Day70|MySQL 分库分表面试题:中间件怎么选?平滑迁移和分片路由全讲透
系列导语:这是一套「从入门到精通」的 Java 面试系列,按先基础后高级、先单点后综合的节奏日更。每天 3–5 道核心题,10–15 分钟读完就能消化吸收。
本文关键词:MySQL 分库分表、分库分表中间件、ShardingSphere、MySQL 平滑迁移、MySQL 分片路由
今日学习目标
今天进入架构层:分库分表。这是"重型武器",也是中高级面试的必考题。今天 5 道题覆盖选型、迁移、拆分策略、路由方式——答出"什么时候不该分库分表",比答出怎么分更有价值。建议先自己口述一遍,再对照解析查漏补缺。
题目1:常用的分库分表中间件有哪些?
【题目】:常用的分库分表中间件有哪些?怎么选型?
【考察意图】:考察你对主流方案的了解,以及是否能根据场景做取舍。
【标准回答】:
| Apache ShardingSphere | JDBC 直连 | Apache 顶级项目 | 绝大多数 Java 项目的首选 |
| MyCat | |||
| TDDL | |||
| Vitess | |||
| PolarDB-X / TDSQL / GaussDB |
ShardingSphere 的两种模式(选型关键)
| ShardingSphere-JDBC | ShardingSphere-Proxy | |
| Jar 包 | 独立进程 | |
| 无网络跳转,损耗最小 | ||
| 仅 Java | 任意语言 | |
| 要改应用依赖并重启 | ||
| 集中配置、统一管控 |
ShardingSphere-JDBC:
应用 ──(嵌入 Jar)──> 数据库集群
ShardingSphere-Proxy:
应用 ──> Proxy ──> 数据库集群选型结论:
• Java 单体 / 微服务,追求性能 → ShardingSphere-JDBC • 多语言团队、需要集中管控 → ShardingSphere-Proxy • 不想自建运维 → 云厂商分布式数据库(PolarDB-X / TDSQL) • 已有 MyCat → 新项目别再引入,老系统逐步迁移
【加分项 / 避坑指南】:
• ✅ 加分:ShardingSphere 早已不只是"分库分表",已发展成生态圈:数据分片、读写分离、分布式事务(XA / Seata)、数据加密、影子库压测、数据脱敏、分布式治理。 • ✅ 加分:"先不要分库分表"才是高手答案。能说出分库分表前的替代手段清单非常加分: 1. 索引优化 + SQL 改写(成本最低,收益最高) 2. 加缓存(Redis 挡读流量) 3. 读写分离(读压力大时) 4. 硬件升级 / 参数调优( innodb_buffer_pool_size等)5. 历史数据归档(单表 5000 万里可能 4000 万是冷数据,归档后根本不用分) 6. 分区表(MySQL 原生 PARTITION,单表逻辑上还是一张表,业务改动小)
→ 这些做完还扛不住,才考虑分库分表。• ⚠️ 避坑:分库分表不是银弹。引入后立刻会面对:SQL 兼容性受限(跨分片 JOIN、子查询、聚合有限制)、分布式事务、全局 ID、扩容数据迁移、运维复杂度翻倍。能用归档和缓存解决的,别分。 • ⚠️ 避坑:MyCat 现在不建议新项目引入。它曾是第一代方案,但社区已基本停滞,遇到坑很难找到维护者。
题目2:分库分表后,如何平滑迁移数据?
【题目】:分库分表后,如何平滑迁移数据(不停机)?
【考察意图】:考察你对数据迁移工程实践的理解,重点看能否保证业务不中断 + 可回滚。
【标准回答】:
核心方法论:双写 + 全量 + 增量 + 校验 + 灰度切换。分四个阶段:
阶段一:双写 + 全量迁移(读旧库)
1. 建好新库表结构(按新分片规则) 2. 改造数据访问层:写操作同时写旧库和新库(新库按新路由规则写入) 3. 全量迁移:用工具(DataX / 自研程序)把旧库历史数据搬到新库 4. 增量同步:订阅 binlog(如 Canal)把迁移期间的新变更补到新库 5. 数据校验:全量或抽样对比,补齐差异
应用 → 写:旧库 + 新库
→ 读:旧库
Canal 订阅旧库 binlog → 补齐新库增量阶段二:双写 + 读新库(灰度)
1. 确认数据一致性和新库性能 OK 2. 灰度切读:先按 1% → 10% → 50% → 100% 的比例逐步把读流量切到新库 3. 每档观察:慢 SQL、错误率、数据一致性 4. 继续双写(关键!这是回滚的保障)
阶段三:停写旧库
1. 确认 100% 读走新库且稳定 2. 停止写旧库,只写新库 3. 观察一段时间
阶段四:旧库下线
1. 确认没有任何业务请求依赖旧库 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 万 | |
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)。
③ 两种拆分对比
| 字段 | 数据行 | |
| 减少 |
【加分项 / 避坑指南】:
• ✅ 加分:垂直分表的底层收益——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 的分片信息,这样"按用户查订单"和"按订单查"都能单分片命中 |
| 非分片键查询怎么办 |
【加分项 / 避坑指南】:
• ✅ 加分:结合实际业务选型—— • 日志、流水、订单:按时间范围分片(天然归档,冷热分离) • 用户、账户、商品:按 ID Hash 分片(访问均匀、无明显热点) • 多租户 SaaS:按 租户 ID 分片(天然隔离) • ✅ 加分:基因法是解决"多维度查询"的经典方案。比如 order_id生成时,把user_id % 16的结果嵌入order_id的低位,那么按order_id % 16和按user_id % 16分片的结果一致,一次查询就能同时满足两种维度。• ⚠️ 避坑:分片键选错 = 灾难。选错之后,业务查询要扫所有分片再在内存聚合(广播查询),性能可能比不分片还差,而且改分片键等于重新分库分表。设计阶段必须把主要查询路径枚举一遍。 • ⚠️ 避坑:跨分片分页/排序/聚合是伪需求。中间件做的方式是"每个分片各自排序 → 汇总 → 再排序",深分页时内存和耗时都会爆炸。业务上要限制翻页深度,或走 ES。
今日小结
今天我们梳理了 分库分表 的 5 个核心考点:
1. 中间件选型——ShardingSphere 是事实标准(JDBC 高性能 / Proxy 多语言);MyCat 不建议新项目引入;云上优先用托管方案 2. 平滑迁移——双写 + 全量 + 增量(Canal)+ 校验 + 灰度切流;新库写失败不能阻断主流程;旧库必须保留可回滚 3. 分库——先垂直(按业务)后水平(按数据);先做索引/缓存/读写分离/归档再考虑分 4. 分表——垂直分表(拆大字段,提升缓存命中);水平分表(分表数取 2 的幂,全局 ID 替代自增) 5. 路由——Hash 取模 / 范围 / 一致性 Hash,外加更实用的翻倍扩容;分片键选型决定成败
记住一条架构主线:分库分表是最后手段。先把索引、缓存、读写分离、历史归档做足,再谈分片。 💪
📌 系列导航:本文为《Java 面试通关指南》第 70 天 / 共 115 天。
明天预告:不停机扩容、百万数据怎么删、读写分离实现、主从延迟怎么处理、分库分表带来的问题——把分库分表的"坑"一次说清。















































































