当电脑刚开始疯狂联网的时候,出现了一个很简单但超级棘手的问题:怎么保证两个在世界不同角落的设备不会生成一样的标识符?
想象一下:两个服务器,一个在东京,一个在柏林,各自独立地给对象生成ID。如果它们碰巧生成了相同的ID——数据就会混乱、被覆盖,系统直接崩了。
所以在90年代,分布式系统和网络协议飞速发展的时候,Microsoft和Open Software Foundation(OSF)内部就诞生了GUID的想法——Globally Unique Identifier,也就是全局唯一标识符。
GUID(在ISO标准里叫UUID,Universally Unique Identifier)其实就是一个128位的数字,比如长这样:
550e8400-e29b-41d4-a716-446655440000
这就像数字指纹一样:又长又乱,碰撞(两个标识符一样)的概率几乎为零。
它的生成方式包括:
- 时间戳,
- 随机数,
- 设备的MAC地址(老版本用),
- 甚至还有加密hash函数。
为啥这很重要?因为GUID让我们可以不用中央服务器、不用协调,也不用锁和等待,就能生成唯一标识符。这对下面这些场景简直救命:
- 分布式数据库,
- 网络协议,
- 文档流转系统,
- 当然还有现代API。
PostgreSQL里的UUID
GUID/UUID现在到处都用——从PostgreSQL到云服务。它们就像现代互联网的无形基石:没有它们,世界根本没法这么容易地连起来。
其实它就是很长的随机数,16字节,用16进制写出来。说白了就是很长的整数。
这种数据类型特别适合你想在整个系统里保证唯一性的时候用,不用依赖什么中心化的ID生成器。分布式系统或者数据在不同服务器上生成的时候尤其有用。
为啥不用INTEGER就行?
你可能会想,既然可以用自增数字当ID,干嘛还要用UUID?咱们来分析下:
全局唯一性:如果你的数据库跑在多个服务器上,INTEGER的唯一性就很难保证。用UUID就没这烦恼。
安全性和数据隐藏:UUID比顺序号难猜多了。这样别人就不容易通过可预测的ID(比如/users/1、/users/2)推测你的数据。
分布式系统:如果数据在系统不同地方生成,自增INTEGER就不行了,除非你搞很复杂的同步。
UUID还有个好处——它是标准,很多编程语言和存储系统都支持。
用UUID的优点
主要优点:
- 唯一性:哪怕数据在不同服务器或系统生成,ID也保证唯一。
- 灵活性:可以用作主键、外键,或者别的用途。
- 可扩展性:分布式数据库场景下特别方便。
不过UUID也有缺点:
- 体积:
UUID占内存16字节,比INTEGER(4字节)大多了。 - 可读性:比起数字,UUID看着难记多了。
PostgreSQL里生成UUID
内置函数gen_random_uuid()
PostgreSQL从13版开始自带gen_random_uuid()函数,可以直接生成随机UUID。这个函数会返回一个唯一标识符,可以直接当UUID类型字段的值用。
例子:
SELECT gen_random_uuid();
结果:
d17fc23b-22e5-4fcb-bf86-1b4c766d77b7
确保安装了pgcrypto扩展(PSQL 1-12用)
在老版本PostgreSQL(13之前),gen_random_uuid()要装了pgcrypto扩展才有。如果公司非让你用老版本,可以用这条命令救场:
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
这样你就能用随机UUID生成了。
把UUID当外键用
UUID很适合用来建立表之间的关系。比如你有个用户表:
| id | name | |
|---|---|---|
| d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | Alice | alice@example.com |
| a1d3e15a-abc1-4b51-a320-2d4c859f7467 | Bob | bob@example.com |
| 3c524998-5c24-4e73-836d-a4c6bb3cafcd | Charlie | charlie@example.com |
创建orders表
我们来建个orders表,user_id当外键,指向users表的id。
| order_id | user_id | order_date |
|---|---|---|
| 1a5b7d9c-b1a2-4f8e-9e7a-0a1111111111 | d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | 2024-10-15 10:00:00 |
| 2b6c8e0d-c2b3-5a9f-af8b-1b2222222222 | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-15 10:05:00 |
| 3c7d9f1e-d3c4-6baf-bc9c-2c3333333333 | 3c524998-5c24-4e73-836d-a4c6bb3cafcd | 2024-10-15 10:10:00 |
| 4d8eaf2f-e4d5-7cb0-cdab-3d4444444444 | d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | 2024-10-15 10:15:00 |
| 5e9fb030-f5e6-8dc1-debc-4e5555555555 | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-15 10:20:00 |
user_id字段和users表的id字段关联,这样就能把用户和他们的订单连起来。
用JOIN查数据
来看看users和orders表里的数据是怎么关联的:
SELECT
u.id AS user_id,
u.name,
o.order_id,
o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id;
结果:
| user_id | name | order_id | order_date |
|---|---|---|---|
| d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | Alice | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-20 12:34:56 |
UUID的常见用法
用户和订单的标识符:分布式系统里,数据可能来自不同地方。
API里的标记:UUID在REST API里经常用来标识实体。
全局数据同步:比如数据从不同服务器收集过来。
常见错误和注意点
手动生成UUID:最好用内置函数,比如gen_random_uuid(),这样不容易出错。
滥用:别在只用自增INTEGER就够的地方用UUID。比如本地表,永远不会扩展的那种。
体积:UUID占空间大,尤其建索引时可能影响查询性能。
GO TO FULL VERSION