📖 本篇简介:分库分表是大数据量场景的终极解决方案,面试常考。本篇整理分库分表的核心概念、分片策略、分布式 ID、分布式事务、跨分片查询等考点,用表格速查。看完这篇,分库分表面试题基本能答上来。
目录
- 速查:分库分表核心概念
- 分片策略对比表
- 分布式 ID 方案对比
- 分布式事务方案对比
- 跨分片查询方案
- 分库分表中间件对比
- 分库分表常见问题
- 面试高频问答
- 自测
速查:分库分表核心概念
分库分表分垂直拆分和水平拆分。垂直拆分按业务拆库,水平拆分按数据行拆表。
| 概念 | 说明 | 适用场景 |
|---|---|---|
| 垂直分库 | 按业务拆库,用户库、订单库、商品库 | 业务边界清晰 |
| 垂直分表 | 把大字段拆到扩展表 | 单表列太多 |
| 水平分库 | 一个库的数据拆到多个库 | 单库数据量大 |
| 水平分表 | 一个表的数据拆到多个表 | 单表数据量大 |
| 分片键 | 分库分表用的字段 | 高频查询字段 |
| 分片数 | 分多少个库表 | 提前规划好 |
| 路由 | 怎么定位数据在哪个分片 | 中间件做 |
| 主键 | 全局唯一 ID | 分布式 ID |
什么时候该分库分表:单表千万级、单库 QPS 到瓶颈、业务垂直拆分后某个模块还是大。
分库分表代价:跨库 join、分布式事务、运维复杂。能不分就不分。
垂直分库例子:用户库、订单库、商品库分开,各管各的。
水平分表例子:订单表按用户 ID 哈希拆成 order_0、order_1 ... order_15。
为什么要分片键:没有分片键,不知道数据在哪个分片,查询要扫所有分片。
分片键不能更新:分片键变了,数据要迁移到别的分片,代价大。
分库分表本质:把一个大库拆成多个小库,每个小库压力小了。
和读写分离的区别:读写分离是一主多从,读扩展;分库分表是把数据拆开,写扩展。
什么时候垂直分库:业务多了,按业务域拆开,各管各的库。
什么时候水平分表:单表数据太大,一个库扛不住。
分库分表的好处:每个分片数据小,查询快,写压力分散。
分库分表的坏处:跨库 join、分布式事务、运维复杂。
垂直分库例子:用户库、订单库、商品库。
水平分库例子:订单表按用户 ID 哈希拆到 db0、db1。
分库分表和读写分离能不能一起用:能,读写分离读扩展,分库分表写扩展。
什么时候用分库分表:单表千万级、单库 QPS 到瓶颈。
分片策略对比表
分片策略选不好,后面全是坑。常见的有这几种。
| 策略 | 原理 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 范围分片 | 按 ID 或时间范围 | 扩容简单 | 热点问题 | 日志、订单 |
| 哈希分片 | 按 ID 哈希取模 | 数据均匀 | 扩容麻烦 | 用户表 |
| 一致性哈希 | 哈希环 | 扩容迁移少 | 实现复杂 | 缓存 |
| 按时间分片 | 按月或按年分表 | 归档方便 | 跨年查询麻烦 | 日志、报表 |
分片键怎么选:选高频查询字段,尽量让查询都落在一个分片。
热点问题:范围分片容易热点,最新数据都打在最新分片。解决:哈希打散。
扩容问题:哈希取模扩容要迁移大部分数据。解决:一致性哈希。
按时间分片例子:日志表按月份分,log_202601、log_202602,老数据直接归档。
范围分片例子:用户 ID 1~100 万在一个分片,100~200 万在下一个。
哈希分片例子:用户 ID % 16,拆成 16 个分片。
按时间分片优缺点:归档方便,但跨年查询要扫很多表。
分片数怎么定:提前规划,一般 4、8、16 个,方便扩容。
分片键选用户 ID 还是订单 ID:看业务,按用户查多就选用户 ID。
热点问题怎么解决:哈希打散,不要按时间连续分。
预分片是什么:提前多分几个分片,后面扩容直接用预留的。
取模扩容为什么麻烦:原来 4 个分片,加到 8 个,每个分片都要迁一半数据。
一致性哈希怎么解决扩容:哈希环上只迁相邻节点的数据,迁移少。
虚拟节点是什么:一致性哈希加虚拟节点,让数据分布更均匀。
按时间分片例子:日志表按月分,log_202601、log_202602。
范围分片热点问题:最新数据都打在最新分片,压力大。
哈希分片优点:数据均匀,没有热点。
-- 哈希分片路由示例(本示例基于 MySQL 8.0)
-- 用户 ID % 16 决定落在哪个分片
SELECT * FROM order_${user_id % 16} WHERE user_id = 123456;
分布式 ID 方案对比
分库分表后,自增主键不能用了,要用分布式 ID。
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| UUID | 随机生成 | 简单 | 太长、无序 |
| 雪花算法 | 时间戳+机器 ID+序列号 | 有序、高性能 | 时钟回拨 |
| 号段模式 | 数据库取号段 | 简单 | 依赖数据库 |
| Redis INCR | Redis 自增 | 简单 | 依赖 Redis |
推荐:雪花算法,高性能、有序、不依赖外部组件。
雪花算法结构:1 位符号位 + 41 位时间戳 + 10 位机器 ID + 12 位序列号。
时钟回拨问题:机器时钟回拨会生成重复 ID。解决:回拨小就等,回拨大就报错。
号段模式原理:数据库存一个最大 ID,应用一次取一段(比如 1000 个),用完再取。
Redis INCR 原理:Redis 计数器自增,性能高,但要持久化防丢。
UUID 为什么不推荐做主键:太长、无序,InnoDB 聚簇索引插入性能差。
号段模式优缺点:简单,数据库压力小,但号段用完要等下次取。
雪花算法性能:单机每秒几百万 ID,完全够用。
雪花算法什么时候用:分布式系统主键不重复的场景。
机器 ID 怎么分配:配置中心统一分配,或者用 IP 哈希。
序列号溢出怎么办:同一毫秒序列号用完,等下一毫秒。
雪花算法 ID 长度:64 位 long 型,MySQL bigint 能存。
为什么不用 UUID 做主键:无序,InnoDB 聚簇索引插入性能差,而且太长。
号段模式例子:数据库表存 max_id,应用一次取 1000 个,用完再取。
Redis ID 缺点:要持久化,不然重启丢 ID。
号段模式优点:数据库压力小,一次取一段。
雪花算法优点:有序、高性能、不依赖外部组件。
-- 本地消息表示例(本示例基于 MySQL 8.0)
CREATE TABLE local_message (
id BIGINT PRIMARY KEY,
biz_id BIGINT,
content VARCHAR(512),
status TINYINT DEFAULT 0, -- 0待发 1已发 2失败
create_time DATETIME
);
分布式事务方案对比
分库分表后,一个事务跨多个库,本地事务搞不定,要用分布式事务。
| 方案 | 原理 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 2PC | 两阶段提交 | 强一致 | 性能差、阻塞 | 对一致性要求极高 |
| TCC | Try-Confirm-Cancel | 灵活 | 业务侵入大 | 资金类 |
| 本地消息表 | 本地事务+消息 | 最终一致 | 有延迟 | 大部分业务 |
| MQ 事务消息 | RocketMQ 事务消息 | 最终一致 | 依赖 MQ | 大部分业务 |
推荐:大部分业务用本地消息表或 MQ 事务消息,最终一致就行。
2PC 为什么不常用:协调者挂了参与者阻塞,性能差,生产很少用。
TCC 适用场景:资金类、库存类,对一致性要求高但又不想用 2PC。
本地消息表原理:本地事务写业务表+消息表,后台任务扫消息表发 MQ,消费方处理。
MQ 事务消息原理:RocketMQ 半消息,本地事务成功再提交消息,失败回滚。
2PC 流程:协调者问所有参与者能提交吗,都回复 yes 再统一提交,有一个 no 就回滚。
最终一致 vs 强一致:大部分业务最终一致就行,不用追求强一致。
本地消息表缺点:有延迟,几秒到几分钟。
TCC 缺点:业务要写三套代码(Try、Confirm、Cancel),侵入大。
2PC 缺点:同步阻塞,协调者单点,性能差。
本地消息表怎么实现:业务表和消息表在同一个本地事务里,后台任务扫消息表发 MQ。
消息消费失败怎么办:重试,重试多次进死信队列人工处理。
最终一致延迟:正常几秒,异常时几分钟到几小时。
TCC 适用场景:下单扣库存、转账,对一致性要求高。
2PC 为什么不生产用:性能差、阻塞、协调者单点。
本地消息表优点:简单可靠,大部分业务够用。
MQ 事务消息优点:不用自己写扫表任务,MQ 帮你做。
-- 游标分页示例(本示例基于 MySQL 8.0)
-- 替代 limit 10000,10,避免深分页性能差
SELECT * FROM orders
WHERE user_id = 123 AND id > 上次最后ID
ORDER BY id LIMIT 10;
跨分片查询方案
分库分表后,跨分片查询是个大问题。
| 方案 | 原理 | 优缺点 |
|---|---|---|
| 中间件路由 | SQL 发到对应分片 | 透明,但不支持复杂 join |
| 业务层聚合 | 应用层查多个分片再聚合 | 灵活,但开发麻烦 |
| 冗余字段 | 把关联字段冗余到分片 | 避免 join,但数据冗余 |
| 宽表 | 一张大表存所有字段 | 查询简单,但更新麻烦 |
建议:尽量让查询都落在一个分片,避免跨分片。
跨分片分页:每个分片查 limit n 条,再归并排序取前 n,性能差。解决:游标分页。
跨分片 count:每个分片 count 再相加,不能精确统计。解决:维护计数表。
跨分片排序:每个分片排序,再归并排序,性能差。解决:尽量用分片键过滤。
跨分片 in 查询:in 条件展开到每个分片,结果归并。
宽表是什么:把关联表的字段冗余到一张大表,避免 join。
冗余字段风险:冗余字段更新要同步,不然数据不一致。
跨分片 sum:每个分片 sum 再相加,结果正确。
跨分片 max/min:每个分片 max 再取 max,结果正确。
跨分片 avg:每个分片 sum 和 count 都查,再算 avg。
跨分片 order by:每个分片排序,再归并排序,性能差。
游标分页是什么:where id > last_id limit n,每次传上次最后一个 id。
为什么游标分页好:不用 offset,直接定位,性能稳定。
宽表缺点:字段冗余多,更新麻烦。
冗余字段缺点:要同步更新,不然数据不一致。
跨分片 sum 怎么算:每个分片 sum 再相加。
分库分表中间件对比
| 中间件 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| ShardingSphere-JDBC | JDBC 层增强 | 轻量、无代理 | 只支持 Java |
| ShardingSphere-Proxy | 代理层 | 多语言支持 | 多一层网络 |
| MyCAT | 代理层 | 功能全 | 性能一般 |
| Vitess | Google 开源 | 成熟 | 复杂 |
推荐:Java 项目用 ShardingSphere-JDBC,轻量无侵入。
ShardingSphere-JDBC 原理:JDBC 驱动层增强,应用直接连分片,无代理,性能好。
ShardingSphere-Proxy 原理:中间代理,应用连 Proxy,Proxy 再连分片,多语言支持。
MyCAT 特点:老牌中间件,功能全,但社区不活跃了,新项目不推荐。
Vitess 特点:Google 开源,YouTube 在用,成熟但部署复杂。
中间件做什么:SQL 解析、路由到正确分片、结果归并。
中间件性能损耗:JDBC 层几乎无损耗,Proxy 层多一跳网络,有损耗。
中间件缺点:SQL 支持有限,复杂 SQL 可能不支持。
什么时候不用中间件:分片规则简单,业务层自己路由就行。
ShardingSphere 生态:JDBC + Proxy,还有治理中心。
分库分表中间件支持读写分离吗:支持,ShardingSphere 自带读写分离。
中间件支持分布式事务吗:支持 Seata 集成,最终一致。
中间件运维成本:多一个组件,多一层故障点。
ShardingSphere-JDBC 缺点:只支持 Java,和应用绑死。
ShardingSphere-Proxy 缺点:多一跳网络,有性能损耗。
分库分表常见问题
分库分表不是银弹,会带来很多问题。
| 问题 | 说明 | 解决 |
|---|---|---|
| 跨分片 join | 关联查询跨库 | 冗余字段、宽表 |
| 分布式事务 | 跨库事务 | 最终一致方案 |
| 分布式 ID | 主键不重复 | 雪花算法 |
| 扩容 | 加分片要迁移数据 | 一致性哈希、预留分片 |
| 分页 | 跨分片分页 | 归并排序、禁止深分页 |
| 分布式 ID | 主键不重复 | 雪花算法 |
| 主键冲突 | 自增主键冲突 | 分布式 ID |
| 扩容迁移 | 数据迁移麻烦 | 双写迁移 |
| 运维复杂 | 分片多了难管理 | 中间件管理 |
| 跨库事务 | 跨库事务不支持 | 最终一致 |
| 全局唯一 | 全局唯一 ID | 分布式 ID |
| 扩容迁移 | 数据迁移不停机 | 双写+切读 |
| 跨库 join | 关联查询跨库 | 冗余字段 |
| 跨库事务 | 跨库事务不支持 | 最终一致 |
| 全局排序 | 跨分片排序慢 | 尽量分片键过滤 |
| 全局 count | 跨分片 count 不准 | 维护计数表 |
| 全局唯一约束 | 跨库唯一约束不支持 | 业务层保证 |
| 跨库分页 | 跨分片分页慢 | 游标分页 |
| 跨库排序 | 跨分片排序慢 | 分片键过滤 |
面试高频问答
分库分表面试高频题,每题一句话答案。
| 问题 | 一句话答案 |
|---|---|
| 什么时候分库分表 | 单表千万级、单库性能瓶颈 |
| 分片键怎么选 | 高频查询字段 |
| 分库分表后怎么做 join | 冗余字段 |
| 分布式 ID 用什么 | 雪花算法 |
| 分布式事务怎么做 | 最终一致 |
| 分布式 ID 用什么 | 雪花算法 |
| 分布式事务怎么做 | 最终一致,本地消息表 |
| 跨分片 join 怎么办 | 冗余字段、宽表 |
| 分库分表后主键怎么办 | 雪花算法 |
| 分库分表中间件选什么 | ShardingSphere-JDBC |
| 分布式事务用什么 | 本地消息表最终一致 |
| 分库分表怎么扩容 | 一致性哈希、双写迁移 |
| 雪花算法有什么问题 | 时钟回拨 |
| 什么是 TCC | Try-Confirm-Cancel |
| 分库分表什么时候用 | 单表千万级 |
| 分片键选什么 | 高频查询字段 |
| 分库分表后怎么做 join | 冗余字段 |
| 分布式 ID 选什么 | 雪花算法 |
| 分库分表中间件选什么 | ShardingSphere |
| 分布式事务怎么做 | 最终一致 |
| 分库分表后怎么做分页 | 游标分页 |
| 雪花算法时钟回拨怎么办 | 等或报错 |
| 一致性哈希是什么 | 哈希环 |
| 分库分表后怎么做扩容 | 双写迁移 |
| 分库分表什么时候用 | 单表千万级 |
| 分片键怎么选 | 高频查询字段 |
| 分库分表后怎么做 join | 冗余字段 |
| 分布式 ID 用什么 | 雪花算法 |
| 分布式事务怎么做 | 最终一致 |
自测
下面 10 道题覆盖分库分表核心考点,先自己想答案,再展开参考答案。
题 1:什么时候要分库分表?
<details>
<summary>参考答案</summary>
单表数据量超过千万级,或者单库 QPS 到瓶颈了。一般经验:单表 500 万行就要考虑,千万级必须分。
</details>
题 2:垂直拆分和水平拆分区别?
<details>
<summary>参考答案</summary>
垂直拆分按业务拆库、按字段拆表;水平拆分按数据行拆,把一张表的数据拆到多个库或多个表。
</details>
题 3:分片键怎么选?
<details>
<summary>参考答案</summary>
选高频查询字段,尽量让查询都落在一个分片。不能选会更新的字段,不然分片键变了数据要迁移。
</details>
题 4:哈希分片和范围分片优缺点?
<details>
<summary>参考答案</summary>
哈希分片数据均匀,但扩容要迁移大量数据;范围分片扩容简单,但容易热点。
</details>
题 5:分布式 ID 用什么方案?
<details>
<summary>参考答案</summary>
推荐雪花算法,时间戳+机器 ID+序列号,有序、高性能、不依赖外部组件。注意时钟回拨问题。
</details>
题 6:分布式事务有哪些方案?
<details>
<summary>参考答案</summary>
2PC 强一致但性能差;TCC 灵活但业务侵入大;本地消息表和 MQ 事务消息是最终一致,大部分业务够用。
</details>
题 7:跨分片 join 怎么办?
<details>
<summary>参考答案</summary>
尽量让 join 的表在同一个分片;实在不行就冗余字段、做宽表;中间件归并性能差,不推荐。
</details>
题 8:分库分表后怎么扩容?
<details>
<summary>参考答案</summary>
一致性哈希扩容迁移少;预留分片法,提前多分几个分片;双写迁移,新老分片同时写,再切读。
</details>
题 9:深分页在分库分表下有什么问题?
<details>
<summary>参考答案</summary>
每个分片都要查 limit 10000,10,再归并排序,性能极差。解决:用游标分页,或者禁止深分页。
</details>
题 10:分库分表中间件怎么选?
<details>
<summary>参考答案</summary>
Java 项目用 ShardingSphere-JDBC,轻量无代理;多语言用 ShardingSphere-Proxy。
</details>
题 11:分库分表后怎么扩容?
<details>
<summary>参考答案</summary>
一致性哈希扩容迁移少;预留分片法提前多分几个分片;双写迁移,新老分片同时写,再切读。
</details>
题 12:什么是 TCC?
<details>
<summary>参考答案</summary>
Try 预留资源、Confirm 确认提交、Cancel 取消回滚。业务侵入大,适合资金类强一致场景。
</details>
题 13:分库分表后怎么做分页?
<details>
<summary>参考答案</summary>
跨分片分页性能差,每个分片都要查 limit n 再归并。解决:游标分页(where id > last_id limit n),或者禁止深分页。
</details>
题 14:什么是一致性哈希?
<details>
<summary>参考答案</summary>
把哈希值组织成环,节点和数据都在环上,顺时针找最近的节点。扩容只迁移相邻节点的数据,比取模迁移少。
</details>
题 15:分库分表后怎么做分布式事务?
<details>
<summary>参考答案</summary>
大部分业务用本地消息表或 MQ 事务消息,最终一致。资金类强一致用 TCC。不用 2PC,性能差。
</details>
题 16:分库分表后怎么做分页?
<details>
<summary>参考答案</summary>
跨分片分页性能差,每个分片都要查 limit n 再归并。解决:游标分页(where id > last_id limit n),或者禁止深分页。
</details>
题 17:分库分表后怎么做 join?
<details>
<summary>参考答案</summary>
尽量让 join 的表在同一个分片;实在不行就冗余字段、做宽表;中间件归并性能差,不推荐。
</details>
题 18:什么是 TCC?
<details>
<summary>参考答案</summary>
Try 预留资源、Confirm 确认提交、Cancel 取消回滚。业务侵入大,适合资金类强一致场景。
</details>
题 19:分库分表后怎么扩容?
<details>
<summary>参考答案</summary>
一致性哈希扩容迁移少;预留分片法提前多分几个分片;双写迁移,新老分片同时写,再切读。
</details>
自测评分:19 道题答对 16 道以上,分库分表这块面试基本稳了;答对 12~15 道,建议把分片策略和分布式事务再看一遍;答对 12 道以下,先把垂直水平拆分区别背熟。
使用建议:分库分表是大招,不到万不得已别用。能加索引、能读写分离解决的问题,就别分库分表。
分库分表前先问自己:数据量真的到瓶颈了吗?加索引能不能解决?读写分离能不能扛?归档冷热数据能不能行?
面试答题技巧:先说什么时候该分,再说怎么分(分片键选什么),最后说带来什么问题(跨分片、分布式事务),怎么解决。
线上经验:分库分表后运维成本翻倍,小公司别盲目跟风。
分库分表后怎么查全表数据:业务层聚合所有分片结果。
分库分表后怎么做报表:用大数据组件,不要在 MySQL 里跑。
分库分表后怎么做数据同步:用 binlog 同步到数仓,不要业务代码双写。
分库分表后怎么做数据备份:每个分片单独备份,不要全库备份。
下一篇预告:第 81 章 速查卡:参数与调优
文章回复
0 条公开回复