分表算法都有哪些?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(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+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:面试官想知道的不只是你会不会写 userId % 4,更想看你是否清楚业界主流的几种分表算法,以及它们各自的适用场景。

  2. 原理理解深度:考察你是否理解每种算法背后的设计动机,为什么要这么分、解决了什么问题。比如一致性哈希为什么会出现,基因法是为了解决什么痛点。

  3. 实战经验:分表算法选型直接决定了扩容难度、跨表查询复杂度、热点数据分布。如果你只知道取模,那面试官会判定你没真正踩过分库分表的坑。

核心答案

业界主流的分表算法主要有 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 % 4userId % 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% 的实际场景。简单总结:

  • 查询模式单一且不扩容 → 取模
  • 数据有时间属性、需要冷热分离 → 范围
  • 节点频繁变化 → 一致性哈希
  • 业务复杂需要人工调配 → 路由表
  • 多维度查询(订单场景) → 基因法

面试高频追问

  1. 追问一:取模分表扩容时怎么办?

    • 方案一:预先多分几张,比如分 1024 张 "虚拟表",物理上合并到 4 张表上(虚拟槽位方案),扩容时只需要把虚拟表重新映射
    • 方案二:双写迁移,新老表同时写,后台慢慢搬迁历史数据,校验完成后切流量
    • 方案三:直接换一致性哈希算法
  2. 追问二:分表键怎么选?

    • 高频查询条件对应的字段,避免广播查询
    • 字段值要离散度高,避免热点
    • 字段要稳定不可变,比如 userId 不能变,手机号这种可以变的别选
  3. 追问三:分表后非分表键的查询怎么办?

    • 广播:所有表查一遍再合并,性能差
    • 二次索引表:建一张 (非分表键, 分表键) 的映射表,先查映射再查目标表
    • 搜索引擎:把数据同步到 ES,用 ES 查到 id 再回查分表
    • 基因法:如果场景合适,直接用基因法绕过这个问题
  4. 追问四:ShardingSphere 默认支持哪些分片算法?

    • PreciseShardingAlgorithm:精确分片(=、IN)
    • RangeShardingAlgorithm:范围分片(BETWEEN、>、<)
    • HintShardingAlgorithm:强制路由
    • ComplexKeysShardingAlgorithm:复合键分片

常见面试变体

  • "分库分表怎么分?分表键怎么选?"
  • "一致性哈希解决了什么问题?"
  • "订单表既要按买家查又要按卖家查,怎么分表?"
  • "分库分表后扩容怎么做?"
  • "为什么不用 UUID 作为分表键?"

记忆口诀

五大算法按场景记

  • 取模:简单均匀,怕扩容
  • 范围:扩容无痛,怕热点
  • 一致性哈希:扩容温柔,要虚节点
  • 路由表:灵活可控,要缓存
  • 基因法:双查神器,电商首选

总结

分表算法没有 "最好的",只有 "最合适的"。面试回答这道题的核心是:先把 5 种算法的名字和适用场景报出来,再选一两种展开讲优缺点和扩容方案。如果能把基因法讲明白,面试官对你的实战经验评分至少加一档。