1. 概述

本文将深入探讨 UUID 和顺序 ID 作为主键时的核心差异。

数据库设计中,主键格式的选择直接影响系统性能、可扩展性和数据完整性。数据库表必须包含唯一且非空的主键列,确保每行记录都能被唯一标识。

选择主键时,核心决策点在于使用 UUID 还是顺序 ID。两种方案各有优劣,最佳选择取决于具体业务场景和系统目标。

2. UUID

根据 RFC 4122 标准定义,UUID(Universally Unique IDentifier)是一个 128 位的值。

当前主流关系型数据库均支持 UUID 类型:

  • Oracle – RAW(16) 类型
  • SQL Server – NEWID() 函数
  • PostgreSQL – UUID 类型
  • MySQL – BINARY(16) 类型或 UUID() 函数

若数据库不支持原生 UUID 类型,应使用 BINARY(16) 存储。避免使用 CHAR(32) 等低效存储方式(虽然可行,但会浪费存储空间)。

下面分析 UUID 作为主键的优缺点:

2.1. 分布式系统

在共享数据库的分布式系统中,UUID 尤其有用。

UUID 作为主键的核心价值在于实现跨系统的数据共享能力。

使用 UUID 可确保主键全局唯一,无需构建中央协调机制管理主键唯一性。同时能简化数据库间的集成复杂度。

分布式数据库和 NoSQL 系统(如 MongoDB 或 CouchDB)普遍采用 UUID 作为键值,而非数值型 ID。

2.2. 唯一性

UUID 具有全局唯一性。这意味着每条记录的 ID 在跨表、跨库甚至跨系统时仍能保持唯一。这在分布式系统中至关重要——节点动态增减时,协调机制复杂,而 UUID 仅在理论上有碰撞可能。

此外,UUID 提供额外安全性:其值难以预测,恶意用户几乎无法猜测 ID。而顺序 ID 的下一个值极易被推测。

UUID 不会泄露业务数据信息,可安全用于 URL 路径。

2.3. 值生成

UUID 的另一优势是可由应用程序或数据库自行生成。

顺序 ID 通常依赖数据库生成和递增,否则跟踪下一个可用值会非常复杂。但这也带来副作用:插入记录后才能获取实际 ID 值。

相反,UUID 可在代码中直接生成,无需数据库介入。因其唯一且无序,无需关注历史值,从而在插入前就能获得主键值,无需等待查询执行。

2.4. 内存占用

UUID 长度 128 位,是 BIGINT 类型的两倍,INTEGER 类型的四倍。

关系型数据库通常用 BIGINT 存储数值型标识符,改用 UUID 仅增加一倍存储空间,影响看似有限。

但过大的主键可能导致性能问题,尤其在查询和索引操作中表现明显。

2.5. 可读性

UUID 由 32 个十六进制数字和 4 个短横线组成,记忆难度极高。

其表示方式对用户不友好,难以口头传达和记忆。顺序 ID 则简单易记。

不过,ID 值本身是否需要人类可读本就值得商榷。

2.6. 排序

UUID 的另一缺陷是无法按自然顺序排序。

此限制可能迫使你添加额外列(如创建时间戳)来实现排序,从而增加查询执行时间。

3. 顺序 ID

顺序 ID 是唯一标识数据库记录的数值型标识符。

由于系统无法为 UUID 生成序列值,顺序 ID 仅支持数值类型。

顺序 ID 因占用空间更小,通常优于 UUID。

主流数据库原生支持顺序 ID:

  • PostgreSQL – SERIAL
  • SQL Server – IDENTITY
  • MySQL – AUTO_INCREMENT
  • SQLite – AUTOINCREMENT

下面分析顺序 ID 作为主键的优缺点:

3.1. 可读性

与 UUID 不同,数值型标识符更易读易记。

通过数值 ID 可轻松追踪记录插入顺序,快速识别记录间的关系。

3.2. 索引效率

主键和外键索引是加速查询和连接的关键。

部分数据库(如 SQL Server 和 MySQL)使用B+树或聚集索引结构。UUID 的索引效率较低:键值越长,索引条目占用内存越多。

此外,UUID 的随机性导致索引因子低。每次表变更都会触发索引更新,影响性能并浪费内存。在连接表上为 UUID 外键建索引会进一步降低性能。

3.3. 批量操作

批量操作将多个数据库操作合并为单一工作单元。

顺序 ID 在批量操作中表现更佳,因其按可预测序列生成。多个操作可合并为单一批次,优化系统性能。

主键按序生成,新记录追加到序列末尾,支持范围查询等需主键排序的操作。同时,顺序 ID 体积小于 UUID,能减少存储占用。

⚠️ 但在分布式系统中需注意序列间隙问题,此时 UUID 可能是更优解。

3.4. 可预测性

顺序 ID 遵循特定结构,具有可预测性。

这可能导致恶意用户获取敏感信息,无意中暴露业务逻辑。例如,最大 ID 可能反映用户订单总量,泄露收入数据。

3.5. 并发控制

如前所述,应用层生成顺序 ID 需查询数据库获取下一个可用值,实现复杂。

在分布式系统中,多节点同时插入数据极易导致键值冲突。解决方案是构建独立服务生成序列值,但该服务会成为单点故障源。

3.6. 大小限制

顺序 ID 存在大小限制。虽然数值范围极大,但仍有耗尽风险。

使用 INT 类型主键时,可能达到最大值(2,147,483,647),导致静默溢出错误,甚至出现负主键值。

4. UUID 与顺序 ID 的核心差异

下表总结两种方案的关键区别:

特性 UUID 顺序 ID
存储空间 128 位 BIGINT: 64 位 / INT: 32 位
碰撞可能性 仅理论可能 因大小限制可能发生
分布式系统支持 ✅ 天然支持 ❌ 需协调组件防重复
NoSQL/分布式数据库 ✅ 广泛支持 ❌ 不推荐使用
可预测性 ❌ 不可预测 ✅ 可预测
可读性 ❌ 难记忆难口头传达 ✅ 易读易记

5. 结论

本文系统分析了 UUID 与顺序 ID 作为主键的差异。

核心结论: 选择取决于具体业务场景和系统目标。

推荐 UUID 的场景:

  • 分布式系统需全局唯一性
  • 需避免 ID 可预测性带来的安全风险
  • 数据库频繁拆分/合并

推荐顺序 ID 的场景:

  • 内存资源严格受限
  • 查询性能是核心指标
  • 需要简单的范围查询和排序

最终决策应基于系统需求与约束的全面评估,避免盲目跟风。在微服务架构日益普及的今天,UUID 的使用场景正在扩大,但顺序 ID 在传统单体应用中仍具优势。


原始标题:UUID vs. Sequential ID as Primary Key