📖 本篇简介:分库分表是应对单表数据量过大、单库写入瓶颈的终极手段,也是中高级面试的架构大题。本篇基于 MySQL 8.0,系统讲解垂直拆分 vs 水平拆分、分片键选择、取模/范围/一致性哈希分片、分库分表带来的分布式 ID、跨片 JOIN、分布式事务、跨片排序分页、扩容与数据迁移、中间件(ShardingSphere/MyCat/Vitess)等核心知识点。学完后你能讲清楚"什么时候该分库分表""分片键怎么选""分库分表后全局唯一 ID 怎么生成""跨片查询怎么处理"等高频面试题。
目录
- 题目 1:什么是分库分表,为什么要做
- 题目 2:垂直拆分与水平拆分的区别
- 题目 3:什么时候该分库分表,阈值是多少
- 题目 4:分片键怎么选,有哪些原则
- 题目 5:取模分片、范围分片、一致性哈希分片对比
- 题目 6:分库分表后全局唯一 ID 怎么生成
- 题目 7:跨片 JOIN 怎么处理
- 题目 8:跨片排序分页怎么实现
- 题目 9:分库分表后分布式事务怎么处理
- 题目 10:分片集群扩容怎么做,数据怎么迁移
- 题目 11:ShardingSphere 与 MyCat/Vitess 对比
- 题目 12:分库分表带来哪些新问题
- 题目 13:非分片键查询怎么优化
- 题目 14:分库分表后主键自增怎么处理
- 题目 15:热点分片问题怎么解决
- 题目 16:分库分表 vs 读写分离怎么选
- 题目 17:布尔表与宽表设计在分库分表下怎么取舍
- 题目 18:分库分表后怎么统计全局 count/sum
- 题目 19:分库分表前要做哪些准备
- 题目 20:常见坑与注意事项
- 速查表:分库分表速记
- 复习清单
- 练习题
题目 1:什么是分库分表,为什么要做
考点
- 单表数据量瓶颈
- 单库写入瓶颈
- 解决手段
参考答案
1. 分库分表:把一个数据库/一张表的数据,按某种规则分散到多个数据库/多张表中。
2. 为什么要做:
- 单表数据量过大(千万~亿级),索引维护慢、查询慢;
- 单库写入 QPS 到瓶颈(单机 5000~10000 TPS);
- 单机磁盘/内存/CPU 上限;
- 业务高增长,垂直扩展到顶。
3. 代价:分布式事务、跨片查询、运维复杂度上升。
| 瓶颈 | 阈值经验 | 解决 |
|------|----------|------|
| 单表行数 | > 2000 万 | 水平分表 |
| 单库 QPS | > 5000~10000 | 分库/读写分离 |
| 单表索引大小 | > 内存 | 分表 |
| 单库磁盘 | > 70% | 分库 |
追问
- 追问:分库分表是银弹吗?应对:不是,能不分就不分,先索引优化、读写分离、缓存,到瓶颈再分。
- 追问:不分库分表单表能扛多大?应对:InnoDB 单表千万级优化好还能跑,过亿就吃力。
题目 2:垂直拆分与水平拆分的区别
考点
- 垂直按业务/字段拆
- 水平按行拆
- 两者常结合
参考答案
| 维度 | 垂直拆分 | 水平拆分 |
|------|----------|----------|
| 拆分方式 | 按业务/字段拆库拆表 | 按行数据分散到多库多表 |
| 解决问题 | 业务耦合、单库连接数 | 单表数据量大、写入瓶颈 |
| 例子 | 用户库、订单库、商品库 | order_00 ~ order_63 |
| 跨片 JOIN | 业务库之间 JOIN 难 | 同库不同表可 JOIN |
1. 垂直分库:按业务拆,用户库、订单库、商品库各自独立。
2. 垂直分表:把一张宽表的大字段(TEXT/BLOB)拆到扩展表。
3. 水平分表:同一张表按行拆成 N 张表(order_00、order_01...)。
4. 水平分库:把表分散到多个数据库实例。
追问
- 追问:先垂直还是先水平?应对:先垂直按业务拆,再对大表水平拆。
- 追问:垂直分表为什么拆大字段?应对:宽表行数据大,buffer pool 缓存效率低。
题目 3:什么时候该分库分表,阈值是多少
考点
- 经验阈值
- 不要过早优化
- 综合评估
参考答案
1. 经验阈值:
- 单表行数 > 2000 万,或单表数据 > 10GB;
- 单库写入 TPS > 5000;
- 单表 DDL 锁表超过秒级;
- 索引大小超过内存。
2. 不要过早分:
- 业务还在早期,数据量增长不确定;
- 索引优化、读写分离、缓存还没做;
- 团队没有分库分表运维经验。
3. 信号:慢查询日志中大量全表扫、DDL 越来越慢、主库 CPU 常年 80%+。
追问
- 追问:2000 万是硬指标吗?应对:不是,要看业务查询模式;如果都是按主键查,几亿行也能跑。
- 追问:分晚了怎么办?应对:在线数据迁移,双写过渡,平滑切流。
题目 4:分片键怎么选,有哪些原则
考点
- 高基数
- 均匀分布
- 高频查询带分片键
- 避免热点
参考答案
1. 原则 1:高频查询都带分片键:否则每次查询都广播所有分片,性能差。
2. 原则 2:分布均匀:避免热点分片(如按性别分片,男/女两片数据不均)。
3. 原则 3:高基数:分片键值要多,才能均匀分散。
4. 原则 4:业务稳定:分片键一旦选定很难改,要选业务上不变的字段(如 user_id)。
5. 常见选择:
- 订单表:user_id(按用户分散);
- 用户表:user_id;
- 日志表:user_id 或时间 + user_id。
追问
- 追问:按时间分片好不好?应对:新数据都进最新分片,热点严重;适合冷数据归档。
- 追问:分片键选错了怎么办?应对:代价巨大,要重新路由 + 数据迁移;所以一开始就要选好。
题目 5:取模分片、范围分片、一致性哈希分片对比
考点
- 取模均匀但迁移麻烦
- 范围易热点
- 一致性哈希减少迁移量
参考答案
| 策略 | 均匀性 | 热点 | 扩容迁移量 | 适用 |
|------|--------|------|-----------|------|
| 取模 N | 好 | 无 | 大量(N 变 2N 几乎全迁) | 稳定分片数 |
| 范围分片 | 差 | 新数据热点 | 小(加新分片) | 时间序列 |
| 一致性哈希 | 中 | 虚拟节点缓解 | 小(只迁移相邻) | 动态扩容 |
1. 取模:user_id % 16,均匀;但分片数从 16 扩到 32,几乎所有数据都要迁移。
2. 范围:按 id 区间分片,新数据都进最后一片,热点;扩容只加新片。
3. 一致性哈希:哈希环 + 虚拟节点,扩容只影响相邻节点,迁移量小。
追问
- 追问:取模扩容怎么平滑?应对:翻倍扩容(16→32),只迁移一半数据;或用一致性哈希。
- 追问:时间序列按什么分片?应对:按月分表(order_202601),冷热分离,但新片热点。
题目 6:分库分表后全局唯一 ID 怎么生成
考点
- 不能自增主键
- 雪花算法
- 号段模式
- UUID 缺点
参考答案
1. 不能用自增:多个分片各自自增会冲突。
2. 常见方案:
- UUID:简单但无序,InnoDB 主键插入随机,索引分裂;
- 雪花算法 Snowflake:64 位 = 时间戳 + 机器 ID + 序列号,趋势递增;
- 号段模式:美团 Leaf、百度 UidGenerator,DB 号段批量取;
- Redis INCR:性能高但依赖 Redis 可用。
Snowflake 64 位 ID 结构:
| 1 bit 符号位 | 41 bit 时间戳 | 10 bit 机器 ID | 12 bit 序列号 |
3. 生产推荐:雪花算法(趋势递增、本地生成无中心)或号段模式。
追问
- 追问:雪花算法时钟回拨怎么办?应对:记录上次时间,回拨时等待或报错;用 Leaf-snowflake 等成熟方案。
- 追问:UUID 为什么不适合 InnoDB 主键?应对:无序导致页分裂、buffer pool 命中率低。
题目 7:跨片 JOIN 怎么处理
考点
- 广播表
- 绑定表
- 应用层组装
- 冗余字段
参考答案
1. 理想:JOIN 条件都带分片键,落在同一片内。
2. 跨片 JOIN 方案:
- 冗余字段:把 JOIN 字段冗余到主表,避免 JOIN;
- 广播表:小表(如字典表)复制到所有分片;
- 绑定表:相关表按相同规则分片(如 order 与 order_item 都按 order_id);
- 应用层组装:先查 A 片拿 id,再查 B 片;
- ER 分片:相关数据同库。
-- 坏:跨片 JOIN
SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id=u.id
-- orders 按 user_id 分片,users 按 user_id 分片 → 同片,可 JOIN
-- 好:绑定表分片规则一致
追问
- 追问:大表之间跨片 JOIN 怎么办?应对:冗余字段或 ES 宽表,别在数据库做跨片 JOIN。
- 追问:广播表更新怎么办?应对:小表低频更新,同步广播到各分片;ShardingSphere 自动处理。
题目 8:跨片排序分页怎么实现
考点
- 各片局部排序
- 归并排序
- 深分页性能差
参考答案
1. 流程:
- 查询下发到所有分片;
- 每个分片按 ORDER BY 排好序,取前 N+offset 行;
- 中间件归并排序,取最终前 N 行。
2. 深分页问题:LIMIT 100000,10 每个分片都要取 100010 行,性能差。
3. 优化:
- 业务上限制最大页数;
- 用游标式分页(WHERE id > last_id LIMIT n);
- 走 ES 做排序分页。
追问
- 追问:跨片 count(*) 怎么算?应对:各片 count 求和,中间件自动做。
- 追问:跨片 GROUP BY 呢?应对:各片局部 GROUP BY,中间件再全局聚合,性能差。
题目 9:分库分表后分布式事务怎么处理
考点
- 本地事务 + 最终一致
- TCC / Saga
- 尽量避免跨片事务
参考答案
1. 原则:尽量让事务落在同一片(按分片键路由),避免跨片分布式事务。
2. 跨片事务方案:
- 2PC/XA:强一致但性能差,生产少用;
- TCC:Try-Confirm-Cancel,业务侵入大;
- Saga:长事务,每步本地事务 + 补偿;
- 本地消息表 / 最终一致:最常用,业务接受短暂不一致。
3. 生产主流:最终一致(消息队列 + 本地消息表)。
追问
- 追问:下单要扣库存 + 写订单,跨库怎么办?应对:按商品 ID 或订单 ID 分片,让两个操作落同库;否则用消息队列最终一致。
- 追问:XA 为什么少用?应对:阻塞、性能差、跨库锁持有久。
题目 10:分片集群扩容怎么做,数据怎么迁移
考点
- 在线扩容
- 双写过渡
- 数据迁移
- 流量切分
参考答案
1. 步骤:
- 新分片部署好;
- 老分片双写(同时写新旧);
- 后台迁移工具把老数据搬到新分片;
- 迁移完成后,读流量切到新分片;
- 观察一段时间,停老分片双写。
2. 工具:ShardingSphere 弹性扩容、DataX、自研迁移工具。
3. 关键:双写期间要保证新旧数据一致,迁移完校验。
追问
- 追问:取模分片扩容迁移量多大?应对:N→2N 迁移一半;取模扩容是分库分表最大痛点。
- 追问:迁移期间业务有影响吗?应对:双写对写性能略影响,读走老分片无影响。
题目 11:ShardingSphere 与 MyCat/Vitess 对比
考点
- ShardingSphere 社区活跃
- MyCat 国产但停滞
- Vitess 云原生
参考答案
| 中间件 | 特点 |
|--------|------|
| ShardingSphere | Apache 顶级项目,JDBC/Proxy 两种模式,社区活跃,国内主流 |
| MyCat | 国产老牌,JVM,社区放缓 |
| Vitess | Google 开源,K8s 友好,超大规模,YouTube 在用 |
1. ShardingSphere-JDBC:以 jar 形式嵌入应用,无代理层,性能好,适合 Java 栈。
2. ShardingSphere-Proxy:独立代理,对应用透明,多语言可用。
3. Vitess:云原生,水平扩展能力强,但架构复杂,适合超大集群。
追问
- 追问:ShardingSphere-JDBC 与 Proxy 怎么选?应对:Java 栈用 JDBC(性能好),多语言用 Proxy。
- 追问:中间件有什么性能损耗?应对:Proxy 模式多一跳网络,约 5%~10% 损耗。
题目 12:分库分表带来哪些新问题
考点
- 分布式事务
- 跨片查询
- 扩容迁移
- 运维复杂
- 分布式 ID
参考答案
1. 分布式事务:跨片事务一致性难保证。
2. 跨片查询:非分片键查询广播所有片,慢。
3. 扩容迁移:数据迁移复杂,取模分片迁移量大。
4. 运维复杂:N 个库 N 套监控备份,故障排查跨片。
5. 分布式 ID:不能自增,要引入 ID 生成服务。
6. 跨片排序分页:深分页性能差。
7. 全局唯一约束:跨片唯一索引难实现。
追问
- 追问:分库分表后唯一约束怎么做?应对:分片键本身唯一;其他唯一键要中间件层校验或单独唯一索引表。
- 追问:分库分表是好事吗?应对:业务到瓶颈必须做,但要充分评估代价。
题目 13:非分片键查询怎么优化
考点
- 广播查询慢
- 冗余字段
- ES 二级索引
- 基因法
参考答案
1. 问题:不带分片键的查询要广播所有分片,性能差。
2. 方案:
- 冗余字段:把高频查询字段作为分片键冗余;
- ES 异构索引:数据同步到 ES,非分片键查询走 ES;
- 基因法:把 user_id 的后几位基因融入 order_id,按 order_id 查询也能路由到对应分片;
- 宽表:把常用字段冗余到一张 ES/宽表。
基因法示例:
user_id = 12345,后 4 位 = 2345
order_id 生成时 = 雪花ID + 2345(后几位)
按 order_id 查询时,取后 4 位 % N = 分片号,路由正确
追问
- 追问:ES 与 MySQL 一致性怎么保证?应对:Canal 监听 binlog 异步同步,业务接受秒级延迟。
- 追问:基因法有什么缺点?应对:ID 设计复杂,扩展性差。
题目 14:分库分表后主键自增怎么处理
考点
- 不能用 AUTO_INCREMENT
- 雪花/号段
- 各片自增步长错开
参考答案
1. 不能全局 AUTO_INCREMENT:各片自增会冲突。
2. 方案 1:全局 ID:雪花算法/号段,应用层生成。
3. 方案 2:各片步长错开(老办法):
- 片 1:auto_increment_increment=16, offset=1;
- 片 2:offset=2;
- 缺点:扩片要重算步长,麻烦。
4. 生产推荐:全局 ID(雪花/号段),与分片解耦。
追问
- 追问:步长法为什么不推荐?应对:扩片要改所有片步长,危险;ID 不连续业务可能有感知。
- 追问:雪花 ID 趋势递增有什么好处?应对:InnoDB 主键插入顺序,减少页分裂,buffer pool 效率高。
题目 15:热点分片问题怎么解决
考点
- 某片数据/访问量远高于其他
- 解决:打散热点
- 时间分片热点
参考答案
1. 热点原因:
- 按时间分片,新数据都进最新片;
- 大 V 用户数据量远超普通用户;
- 取模不均(分片数少)。
2. 解决:
- 打散:分片键后加随机后缀(如 user_id + "_01"),把大用户数据再分散;
- 预分片:一开始就分足够多片(如 64/128),扩容时迁移;
- 热点隔离:大 V 单独分片;
- 一致性哈希 + 虚拟节点:均匀分布。
追问
- 追问:时间分片热点怎么解?应对:按时间 + user_id 复合分片,或新数据进内存库,异步归档。
- 追问:大 V 热点会导致什么?应对:单片 CPU/IO 打满,影响其他用户。
题目 16:分库分表 vs 读写分离怎么选
考点
- 读写分离解决读压力
- 分库分表解决写/容量
- 两者结合
参考答案
| 方案 | 解决问题 | 局限 |
|------|----------|------|
| 读写分离 | 读压力大 | 写瓶颈仍在主库 |
| 垂直分库 | 业务耦合 | 单表数据量仍大 |
| 水平分库分表 | 写瓶颈 + 数据量 | 复杂度高 |
1. 先用读写分离:读多写少,加从库扛读。
2. 再垂直分库:按业务拆库,解决连接数与耦合。
3. 最后水平分表:单表数据量/写入到瓶颈,才水平拆。
4. 生产组合:垂直分库 + 每库读写分离 + 大表水平分表。
追问
- 追问:分库分表后还要读写分离吗?应对:要,每个分片仍主从 + 读写分离。
- 追问:能不能只分表不分库?应对:能,解决单表数据量,但单库写入瓶颈仍在。
题目 17:布尔表与宽表设计在分库分表下怎么取舍
考点
- 宽表减少 JOIN
- 分库分表下宽表更有价值
- 冗余字段同步
参考答案
1. 宽表:把多表 JOIN 的字段冗余到一张表,查询无需 JOIN。
2. 分库分表下优势更大:跨片 JOIN 代价高,宽表直接查一张表。
3. 代价:冗余字段同步(Canal/消息队列),存储成本高。
4. 推荐:核心查询走宽表/ES,OLTP 走分片库。
追问
- 追问:宽表一致性怎么保证?应对:异步同步,接受秒级延迟;强一致场景别用宽表。
- 追问:宽表字段太多怎么办?应对:按业务垂直拆宽表,不要一张表几百列。
题目 18:分库分表后怎么统计全局 count/sum
考点
- 各片聚合后再汇总
- 性能差
- 预聚合表
参考答案
1. 中间件自动做:SELECT COUNT(*) FROM orders ShardingSphere 会下发各片,结果求和。
2. 性能问题:大 count 各片都要扫,慢。
3. 优化:
- 预聚合表:定时任务把各片 count/sum 汇总到汇总库;
- Redis 计数:业务变更时异步更新 Redis;
- 近似值:用各片统计信息估算。
-- 中间件自动下发:
SELECT COUNT(*) FROM orders;
-- 实际执行:
-- order_0: SELECT COUNT(*) FROM order_0
-- order_1: SELECT COUNT(*) FROM order_1
-- ... 结果求和
追问
- 追问:实时求和怎么做到?应对:用 Redis 计数,业务写 DB 后异步更新 Redis。
- 追问:跨片 GROUP BY 呢?应对:各片局部 GROUP BY,中间件再聚合,性能差,建议预聚合。
题目 19:分库分表前要做哪些准备
考点
- 评估是否真要分
- 选分片键
- 中间件选型
- 数据迁移方案
- 业务改造
参考答案
1. 评估:单表数据量、QPS、增长趋势,确认到瓶颈。
2. 分片键选择:高频查询字段,均匀分布。
3. 中间件选型:ShardingSphere/Vitess,团队熟悉度。
4. 全局 ID:接入雪花/号段。
5. 数据迁移方案:双写过渡、数据校验、回滚预案。
6. 业务改造:DAO 层适配、分页排序、分布式事务。
7. 监控运维:跨片监控、慢查询追踪、备份恢复方案。
追问
- 追问:分库分表能回滚吗?应对:能,双写期间可切回老库,但代价大。
- 追问:小团队要分库分表吗?应对:尽量别,复杂度高,先优化单库。
题目 20:常见坑与注意事项
考点
- 分库分表高频坑
- 分片键选错
- 跨片查询
- 扩容迁移
参考答案
1. 坑 1:分片键选错,大部分查询不带分片键,广播所有片。
2. 坑 2:取模分片扩容,几乎全量迁移。
3. 坑 3:按时间分片,新片热点。
4. 坑 4:跨片 JOIN 硬写,性能差。
5. 坑 5:全局 ID 用 UUID,InnoDB 主键性能差。
6. 坑 6:非分片键查询没走 ES,全片广播。
7. 坑 7:分布式事务用 XA,性能差阻塞业务。
8. 坑 8:分库分表后唯一约束没设计,业务唯一性校验缺失。
9. 坑 9:数据迁移没做校验,新旧数据不一致。
10. 坑 10:分库分表后备份恢复复杂,没演练。
追问
- 追问:分库分表后怎么备份?应对:每个分片独立备份,恢复时按分片恢复;比单库复杂。
- 追问:什么时候不该分库分表?应对:数据量小、团队经验不足、业务模型还在变,别过早分。
速查表:分库分表速记
| 决策点 | 建议 |
|--------|------|
| 是否分库分表 | 单表 >2000 万 / 写 QPS >5000 再考虑 |
| 拆分方式 | 先垂直按业务拆,再水平拆大表 |
| 分片键 | 高频查询带它、均匀、高基数、业务稳定 |
| 分片策略 | 稳定用取模,动态扩容用一致性哈希,时间序列按月 |
| 全局 ID | 雪花算法 / 号段模式,别用 UUID |
| 跨片 JOIN | 冗余字段 / 广播表 / ES 宽表 |
| 跨片事务 | 最终一致(消息队列),少用 XA |
| 非分片键查询 | 走 ES / 基因法 / 冗余字段 |
| 扩容 | 双写过渡 + 数据迁移 + 切流 |
| 中间件 | Java 栈 ShardingSphere-JDBC,多语言用 Proxy |
复习清单
| 题目 | 核心结论 | 易错点 |
|------|----------|--------|
| 1 | 单表/单库瓶颈才分 | 过早分库分表 |
| 2 | 垂直按业务,水平按行 | 垂直分表拆大字段 |
| 3 | 2000 万行 / 5000 TPS 经验阈值 | 阈值不是硬指标 |
| 4 | 分片键要高频、均匀、稳定 | 按性别/时间分片热点 |
| 5 | 取模均匀但扩容迁移大 | 一致性哈希虚拟节点 |
| 6 | 雪花/号段生成全局 ID | UUID 无序致索引分裂 |
| 7 | 冗余/广播/绑定表解决跨片 JOIN | 跨片硬 JOIN |
| 8 | 各片局部排序再归并 | 深分页性能差 |
| 9 | 最终一致优于 XA | 跨片事务强一致追求 |
| 10 | 双写过渡 + 迁移 + 切流 | 取模扩容迁移量大 |
| 11 | ShardingSphere 国内主流 | Proxy 模式多一跳 |
| 12 | 分布式事务/跨片/运维三大新问题 | 全局唯一约束缺失 |
| 13 | ES/基因法解决非分片键查询 | 广播所有片 |
| 14 | 全局 ID 与分片解耦 | 步长法扩片危险 |
| 15 | 打散热点、预分片 | 时间分片新片热点 |
| 16 | 读写分离→垂直→水平 | 分库分表后仍需读写分离 |
| 17 | 宽表减少跨片 JOIN | 宽表一致性异步 |
| 18 | 各片聚合再汇总,预聚合优化 | 实时 count 慢 |
| 19 | 分片键/中间件/迁移/业务改造 | 没做回滚预案 |
| 20 | 分片键选错/扩容/跨片三大坑 | 不演练数据迁移 |
练习题
1. 题目:订单表 orders 按 user_id % 16 分 16 片,现在要扩到 32 片,迁移量多大?怎么平滑?
- 参考答案:取模扩容 N→2N 约一半数据要迁移;用双写过渡,后台迁移工具搬数据,校验后切读流量。
2. 题目:分片键选 user_id,但业务经常按 order_id 查询,怎么办?
- 参考答案:用基因法,order_id 后几位融入 user_id 基因,按 order_id 也能路由;或接入 ES 做 order_id 查询。
3. 题目:分库分表后为什么不推荐用 UUID 做主键?
- 参考答案:UUID 无序,InnoDB 聚簇索引插入随机,页分裂、buffer pool 命中率低;用雪花算法趋势递增。
4. 题目:跨片 SELECT COUNT(*) FROM orders WHERE create_time>'2026-10-01' 很慢,怎么优化?
- 参考答案:各片都要扫一遍;建预聚合表定时统计,或用 Redis 计数;复杂查询走 OLAP 库。
5. 题目:分库分表后跨片分布式事务为什么推荐最终一致而不是 XA?
- 参考答案:XA 阻塞、性能差、跨库锁持有久;最终一致(消息队列+本地消息表)性能好,业务接受短暂不一致。
下一篇预告:第 18 章 分布式事务与全局一致性面试精讲
文章回复
0 条公开回复