UUID v4 trùng được không? Collision thật vs myth khi dùng làm ID DB/API

(Cập nhật: 19 tháng 7, 2026 ) UUID UUID v4 collision primary key API database
Kết luận

UUID v4 không phải ‘tuyệt đối không trùng’ theo nghĩa toán học tuyệt đối — không gian 122 bit vẫn hữu hạn. Nhưng với generator đúng chuẩn, an toàn thực dụng cho primary key, request id, và ID phân tán: bạn gần như chắc chắn gặp lỗi con người (truncate, dump trùng, RNG giả) trước khi gặp birthday paradox thật. Đừng chọn serial chỉ vì comment Reddit phóng đại xác suất. Cần sort theo thời gian → UUID v7 / ULID. Sinh và kiểm tra format trên UUID generator Kawa (chạy trình duyệt).

Junior hỏi trong standup: “Hai user trùng UUID thì sao?” Senior trả lời nửa đùa: “Nghỉ việc.” Comment trên forum dán công thức birthday paradox không kèm đơn vị. Bài này tách toán học đúng khỏi nỗi sợ schema — góc nhìn thực tế khi team Việt / outsourcing dùng v4 làm id DB và API.

Ai nên đọc

  • Backend / fullstack chọn primary key cho Postgres, MySQL, Mongo
  • Fresher vừa nghe “UUID không bao giờ trùng” và nửa tin nửa nghi
  • Ai từng thấy “trùng id” rồi phát hiện ra slice(0, 8) hoặc seed test cố định

UUID v4 chứa gì thật sự

UUID là 128 bit. Ở version 4, khoảng 122 bit là ngẫu nhiên; vài bit còn lại mã hóa version và variant. Không gian vẫn khổng lồ: 2¹²² giá trị khả dĩ.

xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
              ^ version = 4
                   ^ variant bits

Ví dụ hợp lệ (chỉ minh họa format):

550e8400-e29b-41d4-a716-446655440000

Nhóm thứ ba bắt đầu bằng 4. Nhóm thứ tư bắt đầu bằng 8, 9, a, hoặc b (variant RFC).

crypto.randomUUID() trên trình duyệt và Node tạo đúng dạng này từ CSPRNG. Chuỗi tự ghép Math.random().toString(16) không được kể cùng câu chuyện collision.

Mở tạo UUID, sinh vài mã, nhìn nibble 4 — docs và mental model phải khớp thứ bạn thực sự emit.

🆔 Tạo UUID ngay tại đây

Số lượng
Kết quả

Birthday paradox bằng ngôn ngữ thường

Collision không phải “xác suất ID tiếp theo trùng một ID cụ thể bạn đang cầm”. Đó là xác suất có ít nhất một cặp trùng trong n mẫu đã sinh.

Hình dungÝ nghĩa
Phòng đông người, ai đó trùng sinh nhật với bạnTăng nhanh theo số người
Một người cụ thể trùng ngày sinh với bạnVẫn nhỏ
Birthday paradox UUIDn lớn mới làm “có cặp nào đó trùng” đáng kể

Với ~122 bit ngẫu nhiên, n mà xác suất collision không còn “đồ chơi” nằm quanh 2⁶¹ — xa tổng bản ghi của hầu hết sản phẩm. Sinh cả tỷ UUID mỗi giây trong nhiều năm vẫn thường dưới vùng đáng sợ đó — và không SaaS nào “lỡ tay” làm vậy.

Quy mô sinh vs mức lo (ước lượng thực dụng)
Số UUID đã sinh Collision (cảm giác) Ghi chú
10⁶ – 10⁹ (SaaS vừa) Gần như 0 Lo bug code trước
10¹⁵ Vẫn cực nhỏ Vượt xa hầu hết hệ thống
~2⁶¹ Bắt đầu 'không còn đồ chơi' Birthday paradox cổ điển

Kết luận thiết kế: Với CSPRNG đúng, “sợ trùng v4” gần như không phải lý do đổi schema. Lý do thật thường là: sort theo thời gian, kích thước khóa, hoặc thói quen team.


Khi “thấy trùng” — checklist thực tế trước khi đổ tội toán học

Hầu hết “collision” trong production là không phải birthday paradox:

  1. Không phải v4 — counter tuần tự mặc UUID, hoặc lib sai version
  2. Truncateid.slice(0, 8) cho URL “đẹp” → không gian entropy sụp
  3. RNG seed cố định trong test lộ sang staging/shared
  4. Import dump hai lần / migrate copy bảng
  5. Client cache một id rồi POST lại nhiều lần (hoặc ngược lại: mỗi retry mint id mới → “trùng nghiệp vụ” không phải trùng UUID)
// NGUY HIỂM: cắt UUID rồi dùng làm khóa
const short = crypto.randomUUID().slice(0, 8); // ~32 bit — dễ đụng

// Đúng hướng: PK đủ v4; slug ngắn là cột/bảng khác
const id = crypto.randomUUID();
const slug = await mintShortSlug(); // entropy riêng, UNIQUE riêng

Trong design review, nói rõ:

  1. Generator nào (crypto.randomUUID / gen_random_uuid() / …)
  2. Có truncate trên đường tới storage không
  3. UNIQUE + xử lý conflict
  4. Client có được mint id không (idempotent POST)
  5. Lo index → bàn v7/ULID, không bàn “xác suất trùng Reddit”

v4 không phải timeline

Tạo sớm hơn  ─✗─>  chuỗi v4 nhỏ hơn theo thứ tự từ điển

ORDER BY id với v4 trông ngẫu nhiên. UI “mới nhất trước” cần created_at hoặc ID có thời gian trong cấu trúc.

So sánh nhanh các kiểu ID
Scheme Collision / unique Sort / đoán được?
UUID v4 Negligible nếu CSPRNG Không sort theo thời gian; khó đoán
UUID v7 Negligible Gần theo thời gian; prefix thời gian lộ một phần
ULID / KSUID Negligible (đủ entropy) Sort theo thời gian; tương tự v7
BIGSERIAL DB cấp unique Sort được; dễ enumerate trên URL công khai
Nanoid ngắn Phụ thuộc độ dài Tùy cấu hình — phải tính entropy

Marketing muốn link ngắn → bảng slug riêng, đừng cắt primary key.


Case study: team outsourcing Việt

Case 1 — “Trùng UUID” trên staging

Team cut uuid.slice(0, 12) cho path /orders/:shortId. Sau vài tuần import CSV, support báo “đơn trùng”. Root cause: không gian ~48 bit + import song song. Fix: PK full v4; short code là cột riêng với UNIQUE và retry khi conflict.

Case 2 — Design review sợ collision → đổi sang serial

Lead đọc comment “UUID sẽ trùng”. Đổi public API sang /users/1, /users/2. Competitor scrape toàn bộ user bằng vòng lặp. Bài học: serial giải sai bài toán; privacy/enumeration và collision là hai trục khác nhau.

Case 3 — Client tự sinh id mỗi lần retry

App mobile mất mạng → retry POST với UUID mới mỗi lần → ba đơn hàng. Fix: client giữ cùng Idempotency-Key / cùng UUID cho đến khi nhận 2xx; server UNIQUE + trả bản ghi đã tạo.


Khi nào dùng v4 làm ID DB/API

Hợp:

  • Microservice / nhiều writer không muốn ID server trung tâm
  • Public id không muốn bị đoán tuần tự
  • Seed data, mock JSON, test fixture cần id hợp lệ nhanh
  • Client mint id + idempotent create

Cân nhắc thay / bổ sung:

  • Bảng write-heavy, quan tâm locality index → v7 / ULID
  • Cần sort “theo id ≈ theo thời gian” trong UI không có created_at
  • Token công khai cực ngắn → phân tích entropy riêng, không cắt v4
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "active",
  "createdAt": "2026-07-19T08:00:00Z"
}

Mock API: dán UUID từ tool — đỡ typo hơn gõ tay.


Checklist trước khi merge schema

  • Generator là CSPRNG / hàm DB chuẩn — không Math.random
  • Không truncate UUID trên đường lưu PK
  • Có UNIQUE (và chiến lược conflict nếu client mint)
  • Document: sort theo created_at hay theo id có thời gian
  • Quyết định client-generated vs server-generated
  • Sinh mẫu, xác nhận nhóm 3 có 4
  • Lo B-tree → bàn v7/ULID, không bàn “myth tuyệt đối không trùng”

Kết

UUID v4 không là “tuyệt đối không trùng” theo nghĩa toán học tuyệt đối — nhưng an toàn thực dụng cho hầu hết DB/API nếu bạn dùng CSPRNG và không tự cắt entropy. Myth nguy hiểm hơn toán: truncate, RNG giả, retry tạo id mới. Chọn serial khi muốn khóa gọn và chấp nhận enumerate; chọn v4 khi muốn unique không cần coordination; chọn v7/ULID khi cần unique locality theo thời gian. Bookmark bài này kèm UUID generator: mỗi RFC schema dán một mẫu thật, nhìn nibble 4, rồi mới tranh luận.