當電腦開始大量連網時,馬上就冒出一個超級簡單但很嚴重的問題:要怎麼保證兩台在世界不同角落的裝置,不會產生一模一樣的識別碼?
想像一下:兩台 server,一台在東京,一台在柏林,各自獨立產生物件的識別碼。如果他們不小心產生一樣的 ID——資料就會搞混、被覆蓋,整個系統直接爆炸。
所以在 1990 年代,分散式系統跟網路協定大爆發的時代,Microsoft 跟 Open Software Foundation(OSF)就搞出了 GUID 這個點子——Globally Unique Identifier,也就是 全域唯一識別碼。
GUID(在 ISO 標準裡叫 UUID,Universally Unique Identifier)就是一個 128 位元的數字,看起來像這樣:
550e8400-e29b-41d4-a716-446655440000
這就像數位指紋一樣:又長又亂,碰撞(兩個識別碼一樣)的機率幾乎是零。
它會用到:
- 時間,
- 隨機數,
- 裝置的 MAC 位址(舊版會用到),
- 甚至還有 cryptographic hash function。
為什麼這麼重要?因為 GUID 讓你可以不用中央 server、不用協調,就能產生唯一識別碼,沒有 lock、沒有延遲。這對於:
- 分散式資料庫,
- 網路協定,
- 文件管理系統,
- 還有現代 API
來說根本救星。
UUID 在 PostgreSQL 裡
GUID/UUID 到處都用得到——從 PostgreSQL 到雲端服務。它們是現代網路的無形建築工人:沒有它們,世界根本沒辦法這麼簡單串在一起。
其實它就是超長的隨機數,16 bytes,用 16 進位寫出來。就是一個超長的整數啦。
這種資料型態超適合你想要在整個系統裡保證唯一值,而且不想靠中央產生器的時候。特別適合分散式系統,或是資料會在不同 server 產生的情境。
為什麼不用 INTEGER 就好?
你可能會想,幹嘛用 UUID,直接用 auto-increment 的數字當識別碼不就好了?來分析一下:
全域唯一性:如果你的資料庫跑在多台 server 上,要保證 INTEGER 唯一很難。用 UUID 這問題就解決了。
安全性跟資料隱藏:UUID 比連號難猜多了。這樣別人就不容易從可預測的 ID 洩漏資訊(像 /users/1, /users/2 這種)。
分散式系統:如果資料在系統不同地方產生,auto-increment INTEGER 沒有複雜同步就不行。
還有一個好處,UUID 是標準,很多程式語言跟資料庫都支援。
用 UUID 的好處
主要優點:
- 唯一性:就算資料在不同 server 或系統產生,也能保證唯一。
- 彈性:可以拿來當 primary key、foreign key 或其他用途。
- 可擴展性:分散式資料庫超方便。
但 UUID 也有缺點:
- 大小:
UUID佔記憶體比較大(16 bytes),INTEGER只要 4 bytes。 - 可讀性:比較難看懂、難記。
在 PostgreSQL 產生 UUID
內建函數 gen_random_uuid()
從 PostgreSQL 13 開始有內建 gen_random_uuid(),可以直接產生隨機 UUID。這個函數會回傳一個唯一識別碼,可以當 UUID 欄位的值。
範例:
SELECT gen_random_uuid();
結果:
d17fc23b-22e5-4fcb-bf86-1b4c766d77b7
確認有裝 pgcrypto extension(PSQL 1-12 用戶)
在舊版 PostgreSQL(13 以前)gen_random_uuid() 要先裝 pgcrypto extension。如果公司硬要你用舊版,可以這樣救場:
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
這樣就能用隨機 UUID 產生器了。
UUID 當 foreign key 用
UUID 很適合拿來做 table 之間的關聯。假設你有個 user table:
| 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 table
來建立一個 orders table,user_id 當 foreign key,指到 users table 的 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 table 的 id 欄位連起來,這樣就能把 user 跟他的訂單關聯起來。
用 JOIN 查資料
來看看 users 跟 orders table 怎麼連:
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 的常見用法
user 跟訂單的識別碼:在分散式系統裡,資料可能來自不同來源。
API 標記:UUID 常常在 REST API 裡拿來當 entity 的識別。
全域資料同步:像是從不同 server 收集資料時。
常見錯誤跟注意事項
手動產生 UUID:最好用內建函數像 gen_random_uuid(),才不會出錯。
用太多:不是每個地方都要用 UUID,有時候 auto-increment INTEGER 就夠了。像是只會在本地用、不會擴展的 table。
大小:UUID 佔比較多空間,特別是有 index 時,可能會影響查詢效能。
GO TO FULL VERSION