So sánh UUID v4 / UUID v7 / ULID: cân bằng tính thời gian, hiệu suất và khả năng đọc
UUID và ULID là cơ chế cấp phát “ID duy nhất” trong hệ thống phân tán và cơ sở dữ liệu. Chúng trông tương tự nhau nhưng khác biệt ở mức độ tiêu chuẩn hóa, hiệu suất lưu trữ, khả năng đọc và rủi ro lộ thông tin. Bài viết này tổng hợp đặc điểm và tiêu chí lựa chọn của ba định dạng chủ đạo — UUID v4, UUID v7, ULID — và cuối cùng bổ sung các ghi chú về UUID v1 / v6.
Bộ công cụ dưới đây cho phép tạo ID và truy xuất thời gian ngay trong trình duyệt, không gửi dữ liệu ra ngoài:
- Trình tạo UUIDv4
- Trình tạo UUIDv7
- Trích xuất thời gian từ UUIDv7
- Trình tạo ULID
- Trích xuất thời gian từ ULID
UUID v4: dạng hoàn toàn ngẫu nhiên
UUID v4 dành 122 trong 128 bit cho số ngẫu nhiên, nên xác suất va chạm cực thấp. Đổi lại, nó không thể sắp xếp theo thời gian tự nhiên, khiến chỉ mục B-Tree thường xuyên tách trang và mất tính cục bộ khi chèn. Thích hợp cho ID phiên, khóa dùng một lần và các trường hợp chỉ cần tính ngẫu nhiên thuần túy.
UUID v7: bước tiến có thể sắp xếp theo thời gian
UUID v7 được chuẩn hóa trong RFC 9562 (2024), với 48 bit đầu chứa mốc thời gian Unix theo đơn vị mili giây và phần còn lại là ngẫu nhiên. Thứ tự từ điển (so sánh chuỗi) khớp với thứ tự tạo ra, nên ngay cả khi dùng làm khóa chính, chỉ mục vẫn giữ được tính cục bộ. Định dạng này cũng có thể lưu trực tiếp vào uuid
hoặc BINARY(16)
hiện có.
- Độ dài phần ngẫu nhiên: trừ đi các bit version/variant, v7 giữ khoảng 74 bit ngẫu nhiên. Một số triển khai áp dụng phát sinh đơn điệu (tránh va chạm trong cùng mili giây) bằng cách điều chỉnh nhẹ các bit, nhưng vẫn đảm bảo phân phối thống kê.
- Rủi ro lộ thông tin: có thể suy ra thời điểm tạo (độ chính xác mili giây) từ ID. Khi phát hành số lượng lớn với PRNG yếu, khả năng suy đoán ID lân cận tăng lên. Trong hệ thống nội bộ điều này thường chấp nhận được, nhưng với endpoint công khai không muốn gợi ý chuỗi tăng dần thì phải cân nhắc.
ULID: ID thân thiện với người đọc
ULID ra đời năm 2016 và trở thành chuẩn de facto. Nó dùng 26 ký tự Crockford Base32 và loại bỏ ký tự dễ nhầm (O/I/L/1), nên dễ nhúng vào URL hay sao chép. Giống v7, 48 bit đầu mang thời gian mili giây, vì vậy thứ tự từ điển của chuỗi chính là thứ tự thời gian.
- Bảo đảm sắp xếp: so sánh chuỗi ULID ở dạng Base32 luôn tương ứng với trình tự thời gian; cơ chế phát sinh đơn điệu cũng rất phổ biến.
- Kiểu lưu trữ:
TEXT(26)
dễ đọc; muốn lưu dạng nhị phân cần chuyển đổi Base32↔16 byte. Chiều rộng vẫn là 128 bit, nhưng kiểuuuid
gốc của nhiều CSDL không tương thích với ULID. - Rủi ro lộ thông tin: giống v7 vì timestamp lộ ra. Nếu ID tài nguyên trong API công khai không được phép để lộ nhịp phát hành, cần cân nhắc kỹ.
Bảng so sánh thực tiễn
Tiêu chí | UUID v4 | UUID v7 | ULID |
---|---|---|---|
Tiêu chuẩn | RFC 4122 | RFC 9562 (2024) | Chưa chuẩn hóa (chuẩn de facto) |
Cấu trúc bit | 122 bit ngẫu nhiên trên 128 | 48bit=UNIX ms + phần còn lại ngẫu nhiên (hỗ trợ đơn điệu) |
48bit=UNIX ms + 80bit ngẫu nhiên |
Entropy ngẫu nhiên | ≈122 bit | ≈74 bit (ước tính sau version/variant) | 80 bit |
Biểu diễn chuỗi | 36 ký tự (hex + dấu gạch) | 36 ký tự (hex + dấu gạch) | 26 ký tự (Crockford Base32) |
Sắp xếp tự nhiên (chuỗi) | × (ngẫu nhiên) | ○ (thứ tự từ điển = thời gian) | ○ (thứ tự từ điển = thời gian) |
Sắp xếp tự nhiên (nhị phân) | × | ○ (BINARY(16) với memcmp tăng dần) |
○ (nếu 6 byte thời gian đầu lưu big-endian thì memcmp tăng dần) |
Lưu trữ trong CSDL | uuid / BINARY(16) là tối ưu |
uuid / BINARY(16) là tối ưu |
TEXT(26) dễ đọc, BINARY(16) cần chuyển đổi / thường không tương thích kiểu UUID |
Độ cục bộ của chỉ mục | Thấp (chèn ngẫu nhiên làm tách trang) | Cao (chèn cuối / lưu ý điểm nóng) | Cao (chèn cuối / lưu ý điểm nóng) |
Chi phí so sánh bằng nhau | Thấp (so sánh 16 byte) | Thấp (so sánh 16 byte) | TEXT cao hơn chút (phụ thuộc collation) / BINARY thấp |
Chi phí mã hóa | Hex↔16 byte (nhẹ) | Hex↔16 byte (nhẹ) | Base32↔16 byte (nặng hơn chút) |
Khả năng đọc / dùng cho URL | Thấp | Thấp | Cao |
Lộ thời gian tạo | Không | Có (mili giây) | Có (mili giây) |
Tình huống tiêu biểu | ID phiên, token dùng một lần | Khóa chính CSDL, ID sự kiện, log theo thời gian | URL, ID công khai, trực quan hóa log |
Ghi chú về điểm nóng của v7/ULID: nếu thao tác chỉ chèn về cuối trên một shard hoặc leader duy nhất, đuôi B-Tree có thể quá nóng. Hãy cân nhắc xáo trộn nhẹ các bit cao (“prefix shuffle”) hoặc dùng thêm khóa sharding.
Ghi chú triển khai trong CSDL (PostgreSQL / MySQL / SQLite)
- PostgreSQL
- v4/v7: nên dùng kiểu
uuid
. Ghi v7 dạng chuỗi vàouuid
giúp tận dụng thứ tự thời gian. - ULID: dùng
char(26)
/text
, hoặc chuyển sangbytea(16)
.
- v4/v7: nên dùng kiểu
- MySQL/InnoDB
BINARY(16)
vừa nhanh vừa gọn cho v4/v7. Với v7, bố cục big-endian cho phép memcmp phản ánh thời gian. ULID có thể để ởCHAR(26)
hoặcBINARY(16)
sau khi chuyển đổi.
- SQLite
- Không có kiểu UUID riêng. Dùng
BLOB(16)
hoặcTEXT
.BLOB
đã lập chỉ mục có hiệu năng khá tốt.
- Không có kiểu UUID riêng. Dùng
Bổ sung về bảo mật
- Chống đoán trước: v7/ULID có cùng phần bit thời gian; nếu phát hành nhiều ID trong cùng mili giây và PRNG yếu, rủi ro đoán ID kế cận tăng. Hãy dùng PRNG mật mã của hệ điều hành và giảm độ lệch khi phát sinh đơn điệu.
- Rò rỉ siêu dữ liệu: từ ID có thể ước lượng thời điểm tạo và số lượng phát hành sơ bộ. Với API công khai, nên tách ID nội bộ và ID công khai.
Khuyến nghị lựa chọn
- UUID v4: hoàn toàn ngẫu nhiên, không sắp xếp được. Vẫn tồn tại xác suất va chạm về lý thuyết. Hợp cho ID phiên hoặc token CSRF.
- UUID v7: UUID có thứ tự và đã chuẩn hóa. Là ứng viên tốt nhất cho khóa chính nội bộ. Phát sinh đơn điệu gần như loại bỏ va chạm trong thực tế.
- ULID: “thân thiện” hơn với người đọc. Hữu ích khi ID cần hiển thị hoặc log cần dễ nhìn, dù thường kém v7 khi làm khóa nội bộ. Cũng hưởng lợi từ phát sinh đơn điệu.
Phụ lục: tóm tắt UUID v1 / v6
-
UUID v1 (thời gian + MAC) Chứa timestamp 60 bit (độ phân giải 100 ns) và định danh nút (thường là địa chỉ MAC). Giữ trật tự thời gian tốt, nhưng có thể làm lộ thông tin máy và cần xử lý cẩn thận khi đồng hồ hệ thống lùi. Ngày nay ít khuyến nghị vì lý do riêng tư.
-
UUID v6 (phiên bản sắp xếp lại của v1) Sắp xếp lại timestamp của v1 theo big-endian để tăng khả năng so sánh từ điển. Từng được coi là “v1 dễ sắp xếp”, nhưng chuẩn hóa cuối cùng tập trung vào v7. Dự án mới thường chọn v7 thay vì v6.
Danh sách kiểm tra triển khai
- Luôn dùng PRNG mật mã do hệ điều hành cung cấp.
- Với phát sinh đơn điệu của v7/ULID, tránh va chạm trong cùng mili giây và hạn chế độ lệch của phần ngẫu nhiên.
- Ưu tiên lưu trong
uuid
/BINARY(16)
để tránh chi phí collation của cột chuỗi. - Đặt chính sách cho ID công khai (có chấp nhận lộ thứ tự phát hành hay không). Khi cần, tách riêng ID nội bộ và ID công khai.
Kết luận: hiện tại UUID v7 là lựa chọn số một cho khóa chính nội bộ. Nếu ưu tiên khả năng đọc hoặc bị ràng buộc kỹ thuật, ULID là phương án đáng cân nhắc. Chỉ khi phải che giấu hoàn toàn timestamp mới cần quay về v4 hoặc phương án khác.