面试记录 2026-09-23
题目:分表后全局 ID 如何生成?
难度:medium
标签:db/sharding, general/knowledge, difficulty/medium
评分:⭐⭐⭐☆☆(3/5)
我的回答
数据分表后,全局 ID 的生成基本上是通过单独的、独立的 ID 生成器去生成,或者是服务下发的时候直接生成,比如雪花算法之类的,去直接生成 ID,然后设置到数据库中。由于使用这种生成性的数据,它还是保,还能保证全局 ID 的自增性,并且在一定区间内保证它数据是稳定的,不像直接使用 UUID 导致数据分散,并且没有实际的业务意义。这些我们通过自定义的方式生成的全局 ID,可以有实际的业务业务意义,并且可以很好的确认数据的位置跟来源。而且它还可以保证自己数据的自征性,可以更好地被业务理解使用。
点评
优点:
- 说出关键方案:雪花算法(Snowflake)——期望答案之一
- 提到雪花算法的自增性、业务语义、数据定位能力
- 主动对比 UUID:分散、无业务意义——理解自增对 B+ 树的重要性
不足:
- 没讲雪花算法原理:64 位 = 1 符号 + 41 时间戳 + 10 机器 ID + 12 序列号
- 没提经典坑:时钟回拨、workerId 冲突、单机 TPS 上限(4096/秒)
- 没提其他方案:号段模式、UUIDv7、Leaf 服务、基因法、数据库 auto_increment 单点
- 没讲"为什么不用数据库自增":分片后每个库自增主键会重复
- 没提选型权衡:性能、有序性、可扩展性、防猜测
补充:
- 六种主流方案:数据库自增、UUID、雪花、号段、Leaf 服务、基因法
- 关键权衡:有序性 + 高性能 + 分布式 + 防猜测 四维矩阵
- 时序型业务(订单/流水)优先雪花/号段;无要求场景可选 UUIDv7
完整讲解
第一部分:知识重点剖析
核心考点定位:能否系统列出主流全局 ID 方案,并说清原理、代价和适用场景。
知识点拆解:
- 雪花算法(Snowflake):Twitter 开源,64 位 ID 组成:
- 1 位符号位(固定 0,正数)
- 41 位毫秒时间戳(约 69 年,足够用)
- 10 位机器 ID(数据中心 5 位 + 机器 5 位,最多 1024 台)
- 12 位序列号(同一毫秒内自增,单机每秒 4096 个 ID)
- 优点:本地生成无网络调用、有序、高性能
- 坑:
- 时钟回拨:NTP 校时可能回拨几秒,导致同 ID 生成;解决:检测回拨后暂停、用备用时间戳、拒绝生成
- workerId 冲突:多实例部署谁发 workerId?常见方案:ZK 临时节点抢、DB 唯一约束、配置文件预分配
- 单机 TPS 上限:4096/秒,高并发要拆到多 worker
- 号段模式(Segment):美团方案
- 数据库单张
ID_SEQ 表存当前号段终点
- 应用本地缓存 1000 个 ID,用完再取下一段
- 优点:有序、性能高、单点数据库 QPS 低(1000 应用请求换 1 次 DB 请求)
- 坑:重启丢未用完的号段(不连续,但唯一);号段大小权衡(太小频繁 DB、太大重启浪费多)
- UUID / UUIDv4:JDK 自带,完全随机
- 优点:分布式天然、防猜测
- 坑:无序导致 B+ 树随机插入,索引碎片严重,写入性能差 3-10 倍
- UUIDv7 改进:前 48 位时间戳 + 后随机,兼具自增和随机
- 数据库 auto_increment 单点:所有分片共用一个 MySQL 自增
- 优点:绝对唯一有序
- 坑:单点瓶颈(一般 5-10w QPS);单点故障;分片后每库自增会重复
- Leaf 服务(美团开源):Leaf-Snowflake(雪花变体,ZK 分配 workerId)+ Leaf-Segment(号段模式,DB 主备切换)
- 基因法(Gene Method):ID 内嵌分片位
- 例:64 位雪花 ID 低 4 位是
hash(buyer_id) % 16
- 优点:路由零成本;不需要查用户表就能定位分片
- 坑:ID 位数受限、主键必须能改(不能用自增)
- 选型四维矩阵:
| 方案 | 有序 | 高性能 | 分布式 | 防猜测 |
|---|
| 数据库自增 | ✅ | ❌ | ❌ | ❌ |
| UUIDv4 | ❌ | ✅ | ✅ | ✅ |
| UUIDv7 | ✅ | ✅ | ✅ | 部分 |
| 雪花 | ✅ | ✅ | ✅ | ❌ |
| 号段 | ✅ | ✅ | ✅ | ❌ |
| 基因法 | ✅ | ✅ | ✅ | ❌ |
第二部分:考察方向与延伸递进
考察方向:
- 深度:能否拆解雪花 64 位组成、讲清时钟回拨处理
- 广度:能否关联 B+ 树写入性能、号段缓存、基因法路由
- 应用:能否为业务场景选型(如订单、日志、评论)
延伸递进面试题:
- 雪花算法遇到时钟回拨怎么办?
- 解答要点:检测回拨 → 暂停几毫秒等待追上;或备用时间戳(如上次成功的时间戳);严重回拨则抛异常
- 号段模式重启会浪费 ID 吗?会不会重复?
- 解答要点:不会重复(DB 有强一致性);会浪费(未用完的号段丢弃);用双 Buffer 减少浪费
- 为什么 UUIDv4 会导致 B+ 树写入性能差 3-10 倍?
- 解答要点:随机分布导致频繁 page split、页内插入需要移动大量数据;UUIDv7 用时间戳前缀修复
第三部分:面试者参考回答(300-600 字口语化)
面试官您好,全局 ID 是分库分表的必备组件,我用六种主流方案来说,重点讲雪花的原理和坑。
为什么必须做全局 ID?分片后每个库都有自己的自增主键,order_0001 和 order_0002 的自增 ID 会重复,业务侧无法用 ID 唯一定位一条记录。所以需要一套跨所有分片唯一的 ID 生成方案。
方案一:数据库自增单点。所有分片共用一个 MySQL 自增列。绝对唯一有序,但单点瓶颈(一般 5-10w QPS 就到头了),也是单点故障源。小业务能用,大业务顶不住。
方案二:UUIDv4。JDK 自带,完全随机,分布式天然。但无序是致命伤——B+ 树随机插入导致 page split 频繁、写入性能差 3-10 倍。除非是纯日志型场景(不需要按 ID 排序),否则不推荐。UUIDv7 修好了这个问题,前 48 位是时间戳,兼具自增和随机。
方案三:雪花算法(Snowflake)——最主流。64 位 ID 分四段:1 位符号 + 41 位毫秒时间戳(约 69 年) + 10 位机器 ID(最多 1024 台机器) + 12 位序列号(单机每秒 4096 个 ID)。优点是本地生成无网络开销、天然有序、性能极高。
雪花的三个坑必须知道:
- 时钟回拨:NTP 校时可能把机器时间往回调几秒,导致同一毫秒生成重复 ID。生产处理:检测回拨 → 拒绝生成,等待追上 → 或用上次成功时间戳的下一个毫秒。严重回拨直接抛异常。
- workerId 分配冲突:多实例部署时谁发 workerId?常见做法是ZK 临时节点抢占(美团 Leaf-Snowflake)或配置文件预分配。ZK 更灵活但要依赖 ZK。
- 单机 TPS 上限 4096:12 位序列号一毫秒只有 4096 个位置,超高并发要拆到多台 worker。
方案四:号段模式。美团另一套方案。数据库里放一张 ID_SEQ 表存当前号段终点,应用本地缓存 1000 个 ID,用完再去取下一段。1000 次应用请求只换 1 次 DB 请求,DB 压力极低。缺点是应用重启会丢掉未用完的号段(不连续,但保证唯一),大业务一般容忍这个 gap。用双 Buffer 预热能进一步减少浪费。
方案五:Leaf 服务(美团开源):把上面两种封装成 RPC 服务,Leaf-Snowflake 负责小流量低延迟,Leaf-Segment 负责高吞吐。生产可用。
方案六:基因法。ID 内嵌分片位——比如 64 位 ID 的低 4 位存 hash(buyer_id) % 16。查订单时截取低 4 位直接路由到对应分片,不需要额外查用户表就能定位数据。电商必备方案,但要求主键能改(自增 ID 不行,得用雪花)。
选型建议:
- 订单、流水、日志:雪花 或 号段(时序要求高)
- 多租户 SaaS:雪花 + 租户 ID 前缀
- 分片键即主键:基因法
- 纯日志无排序要求:UUIDv7
一句话总结:雪花是通用默认选项,其他方案都是针对特定痛点的加强版。生产环境我们默认用 Leaf-Snowflake,超高吞吐场景(如秒杀发券)用 Leaf-Segment,需要路由主键时叠加基因法。