分表算法都有哪些?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
基础掌握度:面试官想知道的不只是你会不会写
userId % 4,更想看你是否清楚业界主流的几种分表算法,以及它们各自的适用场景。 -
原理理解深度:考察你是否理解每种算法背后的设计动机,为什么要这么分、解决了什么问题。比如一致性哈希为什么会出现,基因法是为了解决什么痛点。
-
实战经验:分表算法选型直接决定了扩容难度、跨表查询复杂度、热点数据分布。如果你只知道取模,那面试官会判定你没真正踩过分库分表的坑。
核心答案
业界主流的分表算法主要有 5 种:
| 算法 | 核心思路 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|---|
| 取模分表(Hash Mod) | hash(key) % N |
数据分布均匀、实现简单 | 扩容要迁移大量数据 | 用户 ID 分表 |
| 范围分表(Range) | 按 ID 或时间区间划分 | 扩容无需迁移、范围查询友好 | 容易产生数据热点 | 流水号、日志表 |
| 一致性哈希 | 顺时针找最近节点 | 扩容只影响相邻节点 | 实现复杂、可能不均 | 动态扩缩容场景 |
| 路由表(Lookup) | 维护一张映射表 | 灵活、可手动调配 | 多一次查询、单点风险 | 业务复杂、需要人工干预 |
| 基因法 | 把分表键基因嵌入业务 ID | 多维度查询都能定位到同一表 | ID 生成复杂 | 订单按用户 ID 查 + 按订单号查 |
下面挨个拆。
深度解析
一、取模分表(Hash Mod)
最简单也最常用的一种。思路直接:对分表键做 hash,然后对表数量取模,得到目标表编号。
// 比如 userId = 1001,分 4 张表
int tableIndex = userId.hashCode() % 4;
// 1001 % 4 = 1,落到 table_1
优点:
- 实现极简,一行代码搞定
- 数据分布均匀(前提是分表键本身离散度高)
- 查询时能精确定位到某一张表
缺点:
- 扩容是噩梦:从 4 张表扩到 8 张表,几乎所有的数据都要重新迁移,因为
userId % 4和userId % 8的结果对绝大多数数据都不一样 - 不过有个翻倍扩容规律:4→8 这种翻倍扩容,每个原表只有 50% 的数据需要迁移(新路由
hash % 8的结果恰有一半落在[0, 4)原表区间,另一半落在[4, 8)新表区间),这也是为什么分表数量通常选 2 的幂
适用场景:分表数量相对稳定、短期不扩容的业务,比如按 userId 分表的用户中心。
二、范围分表(Range)
按分表键的范围划分,每个表负责一段区间。
table_0: id ∈ [0, 1_000_000)
table_1: id ∈ [1_000_000, 2_000_000)
table_2: id ∈ [2_000_000, 3_000_000)
...
也可以按时间范围分,比如按月分表:
order_202401: 2024 年 1 月订单
order_202402: 2024 年 2 月订单
优点:
- 扩容零迁移:新数据直接落到新表,老表完全不用动
- 范围查询友好:
where id between 100 and 200能直接定位到某一张表 - 冷热数据天然分离:老表可以归档到便宜存储
缺点:
- 数据热点严重:写操作集中在最新表,老表几乎没流量
- 容易出现单点压力
适用场景:流水表、日志表、按时间归档的历史数据。
三、一致性哈希(Consistent Hash)
这是取模分表的进阶版,专门解决 "扩容要迁移一堆数据" 的问题。
上图展示了一致性哈希的环形结构。把整个哈希空间组织成一个虚拟圆环(通常 0 到 2³²-1),节点和数据都映射到这个环上,数据顺时针找到的第一个节点就是它的归属。
扩容时的影响:
- 新增节点时,只有新节点到顺时针方向下一节点之间的数据需要迁移
- 比如从 4 节点扩到 5 节点,平均迁移率约 23% 左右,远低于取模翻倍扩容的 50%
虚拟节点(Virtual Node):
- 实际工程中节点少的时候,数据分布会严重倾斜
- 解决方案:每个物理节点对应 150~200 个虚拟节点,打散数据
- ShardingSphere、Twemproxy 都用了这个机制
适用场景:节点频繁增减、需要平滑扩缩容的场景,比如缓存集群分片。
四、路由表分表(Lookup Table)
维护一张独立的 "分表路由表",记录每个分表键对应的目标表编号。
路由表 routing_table:
+--------+------------+
| key | table_no |
+--------+------------+
| user1 | 2 |
| user2 | 0 |
| user3 | 3 |
+--------+------------+
优点:
- 极致灵活:可以手动指定某个大客户独占一张表
- 扩容时只需要改路由表,理论上零迁移(实际上还是得迁移)
- 支持业务规则的精细化调配
缺点:
- 多一次查询:每次业务请求都要先查路由表,性能损耗
- 单点风险:路由表挂了整个系统就瘫,必须做高可用和缓存
- 维护成本高
适用场景:业务规则复杂、需要人工干预分表策略的场景,比如多租户 SaaS 系统。
五、基因法(多维度查询的救星)
这个算法是为了解决一个经典难题:订单表既要按 userId 查,又要按 orderId 查,怎么分表才能两边都不扫全表?
核心思路:把 userId 的某些 "基因位"(比如低 4 位)嵌入到 orderId 里,这样不管用哪个字段查,都能从订单号里提取出 userId 的分表基因,定位到同一张表。
// 1. 生成 orderId 时,把 userId 的低 4 位嵌入
long userId = 10086;
int gene = (int)(userId & 0xF); // 取低 4 位 = 6
// 2. 雪花算法生成 orderId 时,把 gene 放进去
long orderId = snowflakeId | gene; // 简化示意
// 3. 按 userId 查
int tableIndex = userId % 16;
// 4. 按 orderId 查,从 orderId 反解出 gene
int extractedGene = (int)(orderId & 0xF);
int tableIndex = extractedGene; // 同一张表!
优点:
- 多维度查询都能精确定位到同一张表,无需双写、无需广播
- 是电商订单分库分表的 "最佳实践",大众点评、美团的订单系统都用这套
缺点:
- ID 生成逻辑复杂,必须改造雪花算法或类似方案
- 基因位数有限,分表数量有上限(4 位基因 = 最多 16 张表)
适用场景:订单表、消息表这种 "既按 A 查又按 B 查" 的双维度查询场景。
算法选型决策流程
这张选型决策图覆盖了 80% 的实际场景。简单总结:
- 查询模式单一且不扩容 → 取模
- 数据有时间属性、需要冷热分离 → 范围
- 节点频繁变化 → 一致性哈希
- 业务复杂需要人工调配 → 路由表
- 多维度查询(订单场景) → 基因法
面试高频追问
-
追问一:取模分表扩容时怎么办?
- 方案一:预先多分几张,比如分 1024 张 "虚拟表",物理上合并到 4 张表上(虚拟槽位方案),扩容时只需要把虚拟表重新映射
- 方案二:双写迁移,新老表同时写,后台慢慢搬迁历史数据,校验完成后切流量
- 方案三:直接换一致性哈希算法
-
追问二:分表键怎么选?
- 选高频查询条件对应的字段,避免广播查询
- 字段值要离散度高,避免热点
- 字段要稳定不可变,比如
userId不能变,手机号这种可以变的别选
-
追问三:分表后非分表键的查询怎么办?
- 广播:所有表查一遍再合并,性能差
- 二次索引表:建一张
(非分表键, 分表键)的映射表,先查映射再查目标表 - 搜索引擎:把数据同步到 ES,用 ES 查到
id再回查分表 - 基因法:如果场景合适,直接用基因法绕过这个问题
-
追问四:ShardingSphere 默认支持哪些分片算法?
PreciseShardingAlgorithm:精确分片(=、IN)RangeShardingAlgorithm:范围分片(BETWEEN、>、<)HintShardingAlgorithm:强制路由ComplexKeysShardingAlgorithm:复合键分片
常见面试变体
- "分库分表怎么分?分表键怎么选?"
- "一致性哈希解决了什么问题?"
- "订单表既要按买家查又要按卖家查,怎么分表?"
- "分库分表后扩容怎么做?"
- "为什么不用 UUID 作为分表键?"
记忆口诀
五大算法按场景记:
- 取模:简单均匀,怕扩容
- 范围:扩容无痛,怕热点
- 一致性哈希:扩容温柔,要虚节点
- 路由表:灵活可控,要缓存
- 基因法:双查神器,电商首选
总结
分表算法没有 "最好的",只有 "最合适的"。面试回答这道题的核心是:先把 5 种算法的名字和适用场景报出来,再选一两种展开讲优缺点和扩容方案。如果能把基因法讲明白,面试官对你的实战经验评分至少加一档。
