← 返回题目列表

号段表发号器如何设计?相比数据库自增主键有什么取舍?

中等 第 25 / 33 题 更新于 2026/07/30
号段模式发号器ID 设计

简化版

号段表发号器是在数据库中维护业务号段,每次应用领取一段 ID 到内存里本地分配,减少频繁访问数据库。它比单次自增发号吞吐更高,也能按业务生成趋势递增 ID,但要处理号段浪费、并发领取和发号服务高可用。

详细版

数据库自增主键简单可靠,但在分库分表、多业务统一 ID、需要提前生成 ID 或跨库写入时会受限。号段模式通过一张表维护 biz_tagmax_idstep,应用每次把 max_id 增加一个步长并拿到区间。

例如当前 max_id=10000,步长 1000,服务领取后数据库更新到 11000,应用可在内存发放 10001 到 11000。这样 1000 次发号只需要 1 次数据库更新。

缺点是服务重启可能浪费未使用号段;如果步长太小,数据库压力仍大;步长太大,浪费和跳号更明显。

完整版教学

一、为什么需要号段模式

自增主键适合单库单表,但复杂系统常需要在插入前拿到 ID,或者多个服务、多个库共享一个业务 ID 规则。每次都访问数据库自增会成为瓶颈。

号段模式的思路是批发 ID。数据库不再每次发一个,而是每次发一段,应用在内存里零成本递增。

数据库发号:一次领取 [10001, 11000]
应用内存:10001, 10002, 10003 ... 11000

如果步长是 1000,数据库更新次数理论上下降 1000 倍。

二、号段表怎么设计

一个常见表结构如下:

CREATE TABLE id_segment (
  biz_tag VARCHAR(64) PRIMARY KEY,
  max_id BIGINT NOT NULL,
  step INT NOT NULL,
  updated_at DATETIME NOT NULL
);

领取号段时用乐观更新或行锁保证并发安全:

UPDATE id_segment
SET max_id = max_id + step
WHERE biz_tag = 'order';

更新成功后读取新的 max_id,区间就是 (old_max_id, new_max_id]

三、并发领取怎么保证不重号

号段表的核心安全点是同一个 biz_tag 的更新必须原子。数据库行锁可以保证两个服务不会拿到同一段。

例如服务 A 和 B 同时领取订单号段。A 先把 10000 更新到 11000,B 之后看到的是 11000,再更新到 12000。两者区间不重叠。

服务领取前 max_id领取后 max_id可用区间
A100001100010001-11000
B110001200011001-12000

这比应用自己查 max 再加一安全得多。

四、步长如何选择

步长太小,频繁访问数据库;步长太大,服务重启或实例下线会浪费大量 ID。选择要看发号 QPS 和可接受跳号程度。

假设订单发号峰值 5000/s,步长 1000,则每秒约领取 5 次号段;步长 10000,则每 2 秒领取一次,但单次浪费上限变大。

很多系统会用双 buffer:当前号段用到 70% 时异步加载下一段,避免号段耗尽时请求阻塞。

五、和自增主键、雪花算法怎么比较

自增主键最简单,强依赖单库;雪花算法不访问数据库,吞吐高,但依赖时钟和机器号;号段模式介于两者之间,趋势递增且实现可控。

方案优点缺点
自增主键简单可靠跨库不方便
号段模式趋势递增、DB 压力低可能跳号、依赖发号表
雪花算法高吞吐、去中心化时钟回拨和机器号管理

面试中要按业务约束选择,不要把某一种说成万能。

六、常见误区与追问

  • 误区:号段模式不会跳号。 服务重启、缓存号段未用完都会造成跳号。
  • 误区:先查 max_id 再 update 就安全。 并发下可能重复,必须用原子更新或锁。
  • 误区:步长越大越好。 步长越大浪费和跳号越明显。
  • 追问:如何减少号段耗尽时的延迟? 用双 buffer,在当前段使用到阈值时异步预取下一段。
  • 追问:号段表挂了怎么办? 已领取号段还能发一段时间,但新号段不可领,需要主从、高可用和告警。

七、加强记忆

记忆钩子:号段模式像批发电影票,售票处一次给你 1000 张,你在门口慢慢发;速度快了,但没发完丢了就会跳号。

回答这题按“为什么要批量发号、表结构、并发安全、步长取舍、方案对比”来讲。它既是数据库设计题,也是高并发 ID 生成题。