当前位置: 首页 > news >正文

主键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万年

相关文章:

  • 华为OD机试A卷 - 积木最远距离(C++ Java JavaScript Python )
  • 文件描述符,它在哪里存的,exec()后还存在吗
  • 【STM32】对stm32F103VET6指南者原理图详解(超详细)
  • 支付页面安全与E-Skimming防护----浅谈PCI DSS v4.0.1要求6.4.3与11.6.1的实施
  • ✨分享我在飞书多维表格中使用DeepSeek的经历✨
  • STM32F103_LL库+寄存器学习笔记05 - GPIO输入模式,捕获上升沿进入中断回调
  • 飞速(FS)InfiniBand解决方案助力领先科技公司网络升级
  • kettle插件-mysql8数据库插件
  • MySQL进阶
  • 【linux复习】——进程间通信
  • 【HarmonyOS NEXT】EventHub和Emitter的使用场景与区别
  • 基于javaweb的SpringBoot雪具商城系统设计与实现(源码+文档+部署讲解)
  • UART(通用异步收发传输器)
  • 删除排序链表中的重复元素
  • CF254B Jury Size
  • 抽象的算法0.1.3.2版本
  • Flutter 完整开发指南
  • 【Qt】C++前向声明与Qt信号与槽的区别
  • 备赛蓝桥杯之第十六届模拟赛2期职业院校组第六题:菜谱教程
  • 【Python】天气数据可视化
  • 泽连斯基启程前往土耳其
  • 黄仕忠丨戏曲文献研究之回顾与展望
  • 网信部门曝光网络谣言典型案例,“AI预测彩票号码百分百中奖”等在列
  • 甩掉“肥胖刺客”,科学减重指南来了
  • 2024年度全国秋粮收购达3.45亿吨
  • 阚吉林任重庆市民政局党组书记,原任市委组织部主持日常工作的副部长