UUID v4 và UUID v7: Nên dùng loại nào làm khóa chính cơ sở dữ liệu?
UUID v4 ngẫu nhiên, UUID v7 sắp xếp theo thời gian. Tìm hiểu cấu trúc của từng loại, vì sao v4 có thể làm chậm chỉ mục database và khi nào v7 (hoặc ULID) là lựa chọn tốt hơn.
Khi thiết kế cơ sở dữ liệu, hầu như ai cũng gặp câu hỏi: dùng số tự tăng hay UUID làm khóa chính? Nếu chọn UUID thì lại có câu hỏi thứ hai: dùng phiên bản nào? Nhiều năm nay câu trả lời mặc định là UUID v4. Nhưng từ khi RFC 9562 (công bố năm 2024) chuẩn hóa UUID v7, rất nhiều đội đã chuyển sang. Bài viết này giải thích lý do.
UUID là gì?
UUID (Universally Unique Identifier) là một giá trị 128 bit, thường được viết thành 32 ký tự thập lục phân chia làm 5 nhóm:
f47ac10b-58cc-4372-a567-0e02b2c3d479
Điểm mạnh lớn nhất của UUID là ai cũng có thể tạo ra mà không cần hỏi một máy chủ trung tâm, nhưng gần như chắc chắn không trùng. Vì vậy UUID rất hợp với hệ thống phân tán, ứng dụng offline, ID công khai trên URL hay khi gộp dữ liệu từ nhiều nguồn.
Hai vị trí nhỏ trong chuỗi chứa thông tin:
- Ký tự đầu tiên của nhóm thứ ba là phiên bản (
4trong ví dụ trên). - Ký tự đầu tiên của nhóm thứ tư cho biết biến thể (
8,9,ahoặcbvới UUID chuẩn RFC).
UUID v4 hoạt động thế nào
UUID v4 rất đơn giản: 122 bit ngẫu nhiên cộng 6 bit cố định cho phiên bản và biến thể. Trong JavaScript chỉ cần một dòng:
crypto.randomUUID(); // "3b241101-e2bb-4255-8caf-4136c566a962"
Xác suất trùng nhỏ đến mức khó tưởng tượng: phải tạo khoảng 2,7 × 10¹⁸ UUID v4 thì mới có 50% khả năng xuất hiện một cặp trùng.
Cái giá ẩn: chèn dữ liệu ngẫu nhiên
Vấn đề của v4 không nằm ở tính duy nhất mà ở thứ tự. Phần lớn cơ sở dữ liệu quan hệ (PostgreSQL, MySQL/InnoDB, SQL Server) lưu khóa chính trong chỉ mục B-tree. Khi khóa tăng dần theo thời gian, bản ghi mới chỉ việc nối vào trang ngoài cùng bên phải của cây, rất rẻ và tận dụng tốt bộ nhớ đệm.
Khóa v4 ngẫu nhiên thì rơi vào bất kỳ đâu trong chỉ mục. Mỗi lần chèn có thể chạm vào một trang khác nhau, dẫn đến:
- Tách trang (page split) liên tục, chỉ mục bị phân mảnh và phình to.
- Bộ nhớ đệm kém hiệu quả: phần "nóng" của chỉ mục là toàn bộ chỉ mục.
- Nhiều thao tác đọc/ghi đĩa hơn khi bảng lên tới hàng triệu dòng.
Với MySQL/InnoDB ảnh hưởng còn rõ hơn, vì bản thân bảng được sắp xếp vật lý theo khóa chính (clustered index).
UUID v7 hoạt động thế nào
UUID v7 giữ nguyên định dạng 128 bit nhưng thay đổi cách bố trí:
| Số bit | Nội dung |
|---|---|
| 48 | Unix timestamp tính bằng mili giây |
| 4 | Phiên bản (7) |
| 12 | Ngẫu nhiên (hoặc bộ đếm trong cùng mili giây) |
| 2 | Biến thể |
| 62 | Ngẫu nhiên |
Vì timestamp đứng đầu, UUID tạo sau sẽ được sắp xếp sau UUID tạo trước. Việc chèn dữ liệu gần giống cột tự tăng, trong khi vẫn giữ mọi ưu điểm của UUID: không cần điều phối tập trung và không lộ số thứ tự dễ đoán.
018bcfe5-6800-7xxx-... ← 2023-11-14 22:13:20 UTC
0192a4b7-1c40-7xxx-... ← thời điểm sau đó, đứng sau khi sắp xếp
Một lợi ích nhỏ nữa: bạn có thể đọc được thời điểm tạo ngay từ ID. Dán một UUID v7 vào công cụ Tạo & Phân tích UUID để xem timestamp được nhúng bên trong.
So sánh nhanh v4 và v7
| UUID v4 | UUID v7 | |
|---|---|---|
| Độ duy nhất | 122 bit ngẫu nhiên | 74 bit ngẫu nhiên mỗi mili giây |
| Thứ tự | Ngẫu nhiên | Gần đúng theo thời gian tạo |
| Hiệu năng chèn vào B-tree | Kém khi dữ liệu lớn | Gần như tuần tự |
| Lộ thời điểm tạo | Không | Có (chính xác tới mili giây) |
| Hỗ trợ sẵn | Ở mọi nơi | Đang tăng (PostgreSQL 18 có uuidv7(), nhiều thư viện) |
Khi nào chọn loại nào
Chọn UUID v7 khi:
- UUID là khóa chính hoặc được đánh chỉ mục trong bảng lớn.
- Bạn muốn bản ghi tự nhiên sắp theo thời gian tạo (tiện phân trang, gỡ lỗi).
- Dự án mới và ngôn ngữ/cơ sở dữ liệu của bạn đã có thư viện v7.
Giữ UUID v4 khi:
- ID không được để lộ thời điểm tạo, ví dụ token đặt lại mật khẩu, mã mời hay dữ liệu nhạy cảm. (Với bí mật, một chuỗi ngẫu nhiên đủ dài thường tốt hơn mọi loại UUID.)
- Bảng nhỏ, hoặc UUID chỉ là thuộc tính phụ ít khi truy vấn.
- Cần tương thích tối đa với hệ thống cũ.
Còn ULID và NanoID?
- ULID giải quyết cùng bài toán với v7 (sắp theo thời gian, 128 bit) nhưng biểu diễn bằng 26 ký tự Crockford Base32, ví dụ
01HF7YAT004E7D9JPFDJKRFPSQ. Ngắn hơn khi đặt trên URL và không phân biệt hoa thường, nhưng không phải UUID chuẩn nên cột kiểuuuidkhông nhận trực tiếp. - NanoID là ID ngẫu nhiên ngắn (mặc định 21 ký tự), rất hợp cho URL công khai, nhưng giống v4 là không có thứ tự.
Mẹo lưu trữ
- Lưu UUID dạng nhị phân, đừng lưu dạng chuỗi. PostgreSQL có kiểu
uuid(16 byte). Với MySQL hãy dùngBINARY(16)thay vìCHAR(36): nhỏ hơn một nửa và so sánh nhanh hơn. - Tạo ID ở tầng ứng dụng khi có thể. Bạn biết ID trước khi chèn, nên dễ tạo các bản ghi liên quan trong cùng một transaction.
- Đừng dựa vào thứ tự tuyệt đối giữa nhiều máy. Đồng hồ có thể lệch, và trong cùng một mili giây thứ tự phụ thuộc phần ngẫu nhiên.
Kết luận
UUID v4 vẫn hoàn toàn ổn cho nhiều trường hợp, nhưng với khóa chính trong bảng ngày càng lớn, UUID v7 là lựa chọn mặc định tốt hơn. Nó giữ được tính duy nhất và phi tập trung của UUID, đồng thời cho cơ sở dữ liệu kiểu chèn tuần tự mà chỉ mục B-tree "ưa thích".
Bạn có thể tạo hàng loạt v4, v7, ULID, NanoID và giải mã bất kỳ UUID nào bằng công cụ Tạo UUID miễn phí. Mọi thứ chạy ngay trong trình duyệt của bạn.
Tác giả
Nguyễn Thành Nam
Full-Stack Engineer · Backend & Platform
Kỹ sư full-stack với hơn 9 năm kinh nghiệm xây dựng API và nền tảng lưu lượng lớn bằng Go, NestJS, PHP (Laravel, Phalcon) và Node.js. Chuyên về thiết kế API GraphQL, hệ thống thời gian thực và tối ưu hiệu năng; xây dựng NICETOOL.dev cho cộng đồng lập trình viên.