主键id设计

主键自增id

🌱 1. 自增 ID(Auto Increment ID)

✅ 特点:

• 数据库自带(MySQL, PostgreSQL 都支持)

• 简单易用,可读性强

• 一般作为主键自带聚簇索引(主键就是物理存储顺序)

❌ 缺点:

单点瓶颈(高并发环境中,写入需要锁定 ID 生成器)

不适合分布式(各节点之间 ID 容易冲突)

不适合高频写入(写热点集中,容易成为瓶颈)

易被推测业务量(连续的 ID 暴露增长速度)

👉 适合:单库/单实例系统,写入压力不大

🌟 什么是 聚簇索引(Clustered Index)?

聚簇索引

将数据本身存储在索引结构中

✅ 索引结构本身就是数据结构,

✅ 数据行存储的物理顺序和索引的顺序一致。

•	普通索引:字典目录页里有词条和页码,真正的内容在其他页(跳转查找)
•	聚簇索引:目录页和内容页是同一页(词条和内容都在一起)

特性说明
✅ 数据和索引一起存储不需要二次跳转,查找快
✅ 按主键顺序存储数据数据文件的物理顺序和主键一致
❌ 每个表只能有一个因为数据只能按一个顺序存一次
✅ 范围查询效率高按顺序扫,非常快
❌ 插入/更新主键成本高因为要“挪位置”
❌ 不适合频繁插入中间位置容易造成 页分裂 + 碎片化
[B+Tree 索引结构]
          [50]
         /    \
     [30]     [70]
    /   \     /   \
  data  data data  data

UUID

🧊 2. UUID(通用唯一标识符)

✅ 特点:

全局唯一,不需要中心节点生成

• 可以离线生成、跨服务生成

• 不泄露业务信息

❌ 缺点:

• 太长(16字节/36字符)

不可读

无序 → 插入时会导致 索引树碎片化、页分裂

占用更多存储(主键 + 索引体积大)

👉 不建议直接作为主键,但可以用作全局唯一业务 ID。

雪花算法

❄️ 3. 雪花算法(Snowflake ID)

这是 Twitter 提出的经典 分布式唯一 ID 生成器,核心思想是:

[时间戳 41bit] + [机器ID 10bit] + [序列号 12bit]

✅ 优点:

• 基于时间,趋势递增,适合数据库主键

• 全局唯一

• 支持高并发(毫秒内可生成 4096 个)

❌ 缺点:

复杂度高,依赖机器时钟(时间回拨会导致重复 ID)

• 有生命周期限制(41bit 时间戳 ≈ 69.7 年)

👉 适合:中大型系统、分布式集群、高并发写入

UUIDv7

UUIDv7 是 UUID 的新标准(RFC 4122 bis),设计目标就是:

兼具 UUID 的全局唯一性 + 雪花 ID 的时间排序性!

✅ 优点:

• 基于时间戳(有序)

• 标准 UUID(仍是 128bit,兼容 UUID 系统)

• 兼顾分布式 + 插入性能 + 全局唯一性

❌ 缺点:

• 还比较新(2022 草案提出,语言/数据库支持不全)

• 时间戳精度有限(约等于毫秒级)

时间驱动的有序 UUID

• UUIDv7 的前缀部分是毫秒级时间戳(48 bit)

• 自然趋势递增、有序

• 是标准 UUID 格式(128 bit,全局唯一)

• 可以被当作主键使用(兼顾唯一性和排序)

PostgreSQL 内置有原生的 uuid 类型,用这个就够了,而且空间更优:

CREATE TABLE my_table (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 替换成你自己的 UUIDv7 生成函数
    ...
);

✅ 原生 UUID 类型:

• 占用 16 字节(128 bit)

• 自动支持索引、排序、比较

• 比 varchar 更节省空间 & 更快

❓2. UUIDv7 插入性能会不会比自增 ID 差?

不会差,反而可以接近持平

UUIDv7 的核心优势就是顺序递增,所以:

• 它生成的值在索引 B+Tree 中的插入是**“顺序插入”** → 极大减少页分裂

• 对比 UUIDv4 的“随机插入”性能好很多

• 和自增 ID 差不多,甚至在分布式场景中还更优(无中心)

❓3. 如果查询按时间来,UUIDv7 的时间前缀能不能用作范围查询?

SELECT * FROM orders
WHERE id >= '018f2d45-...'
  AND id <  '018f2d60-...';

• 如果你使用 WHERE create_time BETWEEN … 也是一样能走索引

• 所以如果查询场景大量是按时间的,加上 create_time 索引是非常合理的

不过如果你已经用了 UUIDv7,而且仅用它做主键并且查询就是时间序排序,那你甚至可以不用额外建 create_time 索引。

🧠 所有 UUID(v1~v8)长度都是固定的:

128 bit(16 字节)

• 通常表示为 36 个字符的字符串(带连字符):8-4-4-4-12 格式

018f2d62-89a7-7b7b-bd5e-bb2d1294f4d3

UUIDv7 开头 48bit 是毫秒级时间戳,所以生成的 UUID 会“大概率保持有序”,也就是**“趋势递增”**。

这就意味着:

插入顺序不会很跳跃 → B+Tree 索引结构能高效维护

• 不容易发生“页分裂” → 插入性能稳定

• 如果你建主键索引(或者唯一索引),性能接近自增 ID

剖析UUIDv7

UUIDv7 的前缀有序、后缀看似“乱序”

| 48 bits  | 4 bits | 12 bits | 62 bits       |
|----------|--------|---------|----------------|
| Unix毫秒时间戳 | version=7 |随机 |更多随机数(熵)|
0195d31a-ff35-7509-80e7-4e4ff946e34d
 ↑      ↑     ↑    ↑     ↑
 |      |     |    |     └─ node/random 数据
 |      |     |    └────── variant 标记(高2位固定)
 |      |     └────────── version 字段 (v7 = 0111)
 |      └───────────────── 高位时间戳后段
 └──────────────────────── 时间戳高位(48 bit)

✅ 时间有序,后缀随机,是有意为之!

• ✅ 前 48 位毫秒时间戳 → 实现整体趋势递增

• ✅ 中间部分固定结构 → version 和 variant

• ✅ 尾部是随机数 → 解决同一毫秒内并发插入冲突(保证唯一性)

时间戳一样的时候,后面的部分是随机的,可能会出现“索引跳插”,这是不是影响性能?

插入场景行为对 B+ Tree 影响
时间不同UUID整体递增插入顺序完美,性能最佳
同一毫秒内多并发插入前缀相同,尾部无序局部跳插(叶子节点)但不分裂,影响极小
高并发压力下UUIDv7 相比 UUIDv4 更稳定更接近“顺序插入”,写性能好很多

🧪 举例解释一下:

假设你当前叶子节点能容纳 100 个 UUID,结果你同一毫秒内来了 50 条记录:

• 它们前 48 bit 一样,后面是随机的 → 插入同一页的不同位置

• B+ 树排序时这些 UUID 会“局部乱序插入”

• 但总体页不会立即分裂 → 插入性能稳定

• 等下一毫秒,下一批 UUID 有新时间戳 → 再进新页

UUIDv7 是一种“

局部无序的有序 ID

不会像 UUIDv4 那样完全乱插导致频繁页分裂

🔚 最终建议总结(含金量超高!)

• ✅ UUIDv7 的“有序 + 唯一”组合,适合作为数据库主键

• ✅ 对 PostgreSQL 而言,使用 UUIDv7 的插入性能 接近自增 ID

• ✅ 不影响索引性能,不必担心页分裂频繁

• ✅ 同一毫秒内的乱序可忽略(除非你 1ms 插入 10000+ 条数据)

• ✅ 若需要精确时间筛选,仍建议保留 create_time 字段 + 索引

✅ PostgreSQL 中的 uuid 类型:

• 占用 16 字节

• 使用原生格式存储(不是字符串!)

• 内部用二进制比较,不需要解析字符串 → 效率比 varchar 快很多

🔍 那 bigint 呢?

✅ bigint 是

64 bit(8 字节)

PostgreSQL 的 bigint(也叫 int8):

• 占用 8 字节

• 存储范围是 -2^63 ~ 2^63 - 1

• 通常用于自增主键(serial8, bigserial)

如果你在 PostgreSQL 中使用原生的 uuid 类型,它天然支持带连字符(-)的标准 UUID 格式,包括 UUIDv7,你可以直接存,也可以直接查!

INSERT INTO users (id, name)
VALUES ('0195d31a-ff35-7509-80e7-4e4ff946e34d', 'Alice');

但是:推荐使用带 - 的标准格式,更可读、兼容性好、调试更方便。

四个对比

特性自增 IDUUID雪花 IDUUIDv7
唯一性单库唯一全局唯一全局唯一全局唯一
顺序性有序无序趋势递增有序
可读性一般一般
性能
分布式支持
作为主键插入效率
标准化支持否(私有实现)是(UUIDv7)
生命周期限制~70年~20万年
### Redis 主键 ID 的实现与用法 Redis 是一种高性能的内存数据库,广泛用于缓存、消息队列以及分布式锁等场景。关于主键 ID 的生成和管理,以下是几种常见的实现方式及其具体应用。 #### 一、基于计数器的自增主键 通过 `INCR` 命令或者封装后的工具类(如引用中的 `RedisUtils.getIncr` 方法),可以轻松实现全局唯一的自增主键[^2]。 这种方法的核心在于维护一个特定的键值对,每次调用时对该键执行原子性的加一操作,并返回最新的值作为新记录的主键。例如: ```java public static int getIncr(Class<?> clazz) { String key = clazz.getSimpleName() + ":id"; return (int) redisTemplate.opsForValue().increment(key, 1); } ``` 上述代码片段展示了如何针对不同的实体类动态生成独立的自增序列号。 #### 二、基于队列的消息传递模型 另一种常见的方式是借助 Redis 队列功能分配主键。这种方式适用于多线程或多节点环境下的并发控制需求[^3]。 下面是一个简单的 Java 实现示例: ```java RQueue<Long> pkQueue = redisson.getQueue("GENERATOR_PRIMARY_KEY"); if (!pkQueue.isEmpty()) { Long primaryKey = pkQueue.poll(); System.out.println("Generated Primary Key: " + primaryKey); } else { throw new RuntimeException("Primary key queue is empty!"); } ``` 此方法的优点是可以预加载一批主键放入队列中,从而减少频繁访问 Redis 所带来的性能开销。 #### 三、UUID 方式的无序唯一标识符 尽管 UUID 不具备顺序性和紧凑性特点,但它仍然是一种简单有效的解决方案,尤其适合那些不需要连续编号的应用场合[^4]。Java 中可以直接使用 JDK 自带的功能生成随机字符串形式的 UUID: ```java String uniqueId = java.util.UUID.randomUUID().toString(); System.out.println(uniqueId); // 输出类似于 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' ``` 需要注意的是,由于其长度较长且不具有递增性质,因此通常不会被选作传统意义上的“主键”,但在某些特殊情况下仍可作为一种备选项考虑。 --- ### 总结 综上所述,Redis 支持多种灵活高效的主键生成机制,开发者可以根据实际业务需求选择合适的技术路线。对于追求高效能与低延迟的服务来说,采用 **计数器模式** 或者 **队列分发方案** 更为推荐;而当面对高可用性要求较高的分布式架构设计时,则可能更倾向于依赖于像 Snowflake Algorithm 这样的算法来完成跨数据中心级别的统一身份编码工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值