Dùng UUID v4 khi bạn chỉ cần một mã định danh duy nhất và thứ tự không quan trọng. Dùng UUID v7 khi mã đó là khóa chính của bảng trong cơ sở dữ liệu, vì nó bắt đầu bằng thời gian và hàng mới luôn rơi vào cuối chỉ mục. Dùng ULID khi muốn cùng cách sắp theo thời gian ấy trong dạng chữ ngắn hơn, 26 ký tự. Dùng chuỗi kiểu NanoID cho các mã công khai ngắn như liên kết mời. Bài này giải thích mỗi loại được cấu tạo ra sao, xác suất trùng thật sự là bao nhiêu, vì sao lựa chọn ảnh hưởng đến tốc độ cơ sở dữ liệu, và những việc không loại nào nên dùng để làm.
UUID là gì
UUID dài 128 bit, viết bằng 32 chữ số thập lục phân theo nhóm 8-4-4-4-12, ví dụ 550e8400-e29b-41d4-a716-446655440000. Có hai vị trí mang ý nghĩa. Chữ số đầu của nhóm thứ ba là phiên bản: ở ví dụ trên là 4. Chữ số đầu của nhóm thứ tư là biến thể, và với mọi UUID đang dùng hiện nay nó là 8, 9, a hoặc b. Chuẩn hiện hành là RFC 9562, công bố năm 2024, thay thế RFC 4122 và bổ sung các phiên bản 6, 7 và 8 bên cạnh các phiên bản cũ.
GUID, tên gọi của Microsoft, là cùng một thứ, đôi khi viết hoa và đặt trong dấu ngoặc nhọn.
UUID v4: 122 bit ngẫu nhiên
UUID v4 ngẫu nhiên trừ sáu bit cố định, bốn bit cho phiên bản và hai bit cho biến thể. Còn lại 122 bit ngẫu nhiên, tức khoảng 5,3 × 10^36 giá trị có thể có. Khi sinh bằng nguồn ngẫu nhiên tốt, như bộ sinh mật mã tích hợp trong trình duyệt, đây là lựa chọn mặc định vì mọi ngôn ngữ, cơ sở dữ liệu và framework đều hỗ trợ.
Xác suất trùng thật sự là bao nhiêu
Với n giá trị ngẫu nhiên rút từ N khả năng, xác suất có hai giá trị bất kỳ trùng nhau xấp xỉ 1 − e^(−n(n−1) / 2N), ước lượng "nghịch lý ngày sinh". Với N = 2^122, tính bằng số học độ chính xác cao:
| Số UUID đã sinh | Xác suất có ít nhất hai cái trùng |
|---|---|
| 1 triệu | khoảng 9,4 × 10^-26 |
| 1 tỷ | khoảng 9,4 × 10^-20 |
| 1 nghìn tỷ | khoảng 9,4 × 10^-14 |
| 1 triệu tỷ | khoảng 9,4 × 10^-8 |
| 1 tỷ tỷ (10^18) | khoảng 9 phần trăm |
Xác suất chỉ đạt 50 phần trăm sau khoảng 2,7 × 10^18 UUID. Với tốc độ một tỷ mỗi giây, mất chừng 86 năm. Trong thực tế, trùng lặp không phải thứ hay hỏng. Thứ hay hỏng là nguồn ngẫu nhiên kém, như Math.random() trong thư viện cũ, hoặc một ảnh máy ảo bị nhân bản cùng với trạng thái ngẫu nhiên của nó. Các ước lượng trên giả định bộ sinh tốt, nên dù sao cũng hãy đặt chỉ mục duy nhất trong cơ sở dữ liệu: nó rẻ và biến một sự kiện cực kỳ hiếm thành một lỗi rõ ràng.
UUID v7: thời gian đứng đầu
UUID v7 bắt đầu bằng dấu thời gian 48 bit tính bằng mili giây từ năm 1970, rồi 4 bit phiên bản, 2 bit biến thể và 74 bit ngẫu nhiên hoặc bộ đếm. Ví dụ trong chuẩn là 017f22e2-79b0-7cc3-98c4-dc0c0c07398f. 12 chữ số đầu của nó, 017f22e279b0, là 1.645.557.742.000 mili giây sau epoch, tức 19:22:22 UTC ngày 22 tháng 2 năm 2022. Trường 48 bit này đủ dùng đến năm 10889.
Vì dấu thời gian đứng trước, các giá trị v7 sắp xếp theo thứ tự tạo, dù xét như chuỗi chữ hay nhị phân. Các ID tạo trong cùng một mili giây cần cách phân định. Bộ sinh bên dưới đếm tăng phần ngẫu nhiên lên một đơn vị, nên cả lô ra theo thứ tự. Một lô tạo ngày 13 tháng 10 năm 2026 trông như sau:
01a1355c-a15a-711d-b937-2da9e4688fc1
01a1355c-a15a-711d-b937-2da9e4688fc2
01a1355c-a15a-711d-b937-2da9e4688fc3Để so sánh, một lô v4 không có điểm chung nào:
ecd64af2-9621-42d4-b5a5-7cc064b76f66
cb382b5b-a904-4799-96c0-d610b88d93bf
2817867b-4336-40ad-ae08-96381f6567ebULID: cùng ý tưởng trong 26 ký tự
ULID có dấu thời gian 48 bit tính bằng mili giây và 80 bit ngẫu nhiên, viết bằng 26 ký tự Crockford base32, bảng này bỏ các chữ I, L, O và U để khó đọc nhầm. Nó sắp theo thứ tự bảng chữ cái cũng chính là theo thời gian. 10 ký tự đầu là thời gian: 01ARZ3NDEK là 1.469.922.850.259 mili giây, tức ngày 30 tháng 7 năm 2016. Nó không phải UUID, dù cũng có 128 bit và lưu được vào cột UUID. Ưu điểm chính là chuỗi ngắn hơn và không có dấu gạch nối. Nhược điểm chính là ít công cụ và thư viện biết đến nó hơn.
Chuỗi kiểu NanoID
NanoID là chuỗi ngẫu nhiên trên bảng 64 ký tự an toàn cho URL. Mặc định 21 ký tự mang 21 × 6 = 126 bit, ngang một UUID, nhưng chỉ trong 21 ký tự. Nó không có thứ tự thời gian và không có dấu phiên bản, nên hợp với mã công khai ngắn: liên kết chia sẻ, mã mời, mã đơn hàng ngắn. Đừng dùng nó làm khóa chính khi tốc độ ghi quan trọng, vì lý do ở phần kế tiếp.
Vì sao lựa chọn ảnh hưởng tốc độ cơ sở dữ liệu
Đa số cơ sở dữ liệu giữ khóa chính trong chỉ mục B-tree, một cây các trang đã sắp xếp. Khóa tuần tự, như số tự tăng hay UUID v7, luôn chèn vào mép phải. Vài trang đó luôn nằm trong bộ nhớ, đầy dần và không bị đụng đến nữa. Khóa ngẫu nhiên như v4 mỗi lần chèn vào một chỗ ngẫu nhiên trong cây. Khi chỉ mục lớn hơn bộ nhớ, mỗi lần chèn có thể phải đọc một trang từ đĩa, các trang bị tách giữa chừng và còn lại nửa trống, và chỉ mục phình to hơn mức cần thiết. Đó là vấn đề "tính cục bộ của chỉ mục". Với các hệ lưu bảng theo thứ tự khóa chính, như InnoDB của MySQL, cả bảng bị ảnh hưởng chứ không riêng chỉ mục.
Mức ảnh hưởng phụ thuộc kích thước. Bảng vài nghìn hàng sẽ không thấy gì. Bảng hàng trăm triệu hàng với khóa v4 thì thường thấy. Thứ tự còn có ích trong công việc hằng ngày: các hàng mới nằm cạnh nhau, và "100 bản ghi gần nhất" là một lần quét khoảng rất rẻ.
Hỗ trợ đã tăng lên. PostgreSQL từ lâu có gen_random_uuid() cho v4, và PostgreSQL 18 thêm hàm uuidv7() tích hợp sẵn; với bản cũ hơn, bạn sinh v7 trong ứng dụng hoặc bằng tiện ích mở rộng. Hãy lưu UUID bằng kiểu 16 byte gốc của cơ sở dữ liệu nếu có, đừng lưu 36 ký tự chữ.
Không loại nào dùng để làm những việc này
- Bí mật. UUID là mã định danh, không phải mật khẩu. v7 và ULID để lộ thời điểm ID được tạo. Trong cùng một mili giây, bộ đếm khiến các ID liền kề đoán được từ nhau. UUID v4 có 122 bit ngẫu nhiên, nhưng cứ coi nó là mã định danh. Với mật khẩu hay token đặt lại, hãy dùng công cụ tạo mật khẩu; bài mật khẩu mạnh cần những gì giải thích vì sao độ dài và tính ngẫu nhiên quan trọng.
- Phân quyền. Biết một ID không bao giờ được phép đủ để đọc bản ghi. Hãy kiểm tra quyền ở mọi yêu cầu.
- Logic nghiệp vụ. Đừng đọc dấu thời gian từ ID để quyết định điều gì; hãy lưu cột
created_atthật. Đọc nó để gỡ lỗi thì được.
Chọn loại nào
| Bạn cần | Dùng |
|---|---|
| ID duy nhất và thứ tự không quan trọng | UUID v4 |
| Khóa chính của bảng bận rộn | UUID v7 |
| Sắp theo thời gian, chữ ngắn hơn, không gạch nối | ULID |
| Mã công khai ngắn trong URL | Chuỗi kiểu NanoID |
| Bí mật, mật khẩu hoặc token | Không loại nào ở đây: dùng công cụ tạo mật khẩu |
Tạo chúng bằng công cụ
- Mở công cụ tạo UUID và chọn Loại: UUID v4 (ngẫu nhiên), UUID v7 (theo thời gian), ULID hoặc Kiểu NanoID.
- Đặt Số lượng, từ 1 đến 1000. Với NanoID, đặt Độ dài từ 1 đến 128.
- Với UUID, chọn Chữ hoa, tắt Dấu gạch nối, hoặc thêm Dấu ngoặc { } cho kiểu GUID của Windows. Bản thân ID không đổi, chỉ cách viết thay đổi.
- Bấm "Sao chép tất cả" để có mỗi ID một dòng, hoặc tải tệp TXT, hoặc tệp CSV có tiêu đề
idcho bảng tính hay tập lệnh nạp dữ liệu. "Tạo lại" cho một lô mới.
Số ngẫu nhiên đến từ bộ sinh mật mã của trình duyệt, và không có gì rời khỏi trang. 12 chữ số thập lục phân đầu của UUID v7 là một dấu thời gian mili giây: đổi chúng sang số thập phân rồi công cụ đổi dấu thời gian sẽ hiện ngày giờ, và bài giây và mili giây giải thích các đơn vị.
Những lỗi thường gặp
- So sánh ID như chuỗi chữ mà không chuẩn hóa. UUID là một dù viết hoa hay thường, có hay không có gạch nối hoặc ngoặc. Hãy lưu một dạng chuẩn, thường là chữ thường có gạch nối.
- Dùng v4 làm khóa chính của bảng rất lớn rồi thắc mắc vì sao chèn chậm. Hãy cân nhắc v7.
- Sắp xếp các giá trị v4 và mong đợi thứ tự tạo. Chúng không mang thứ tự nào.
- Bỏ qua chỉ mục duy nhất. Tính ngẫu nhiên cho xác suất, không cho bảo đảm.
- Dùng hàm ngẫu nhiên yếu. Hãy lấy ID từ nguồn mật mã.
Danh sách kiểm tra ngắn
Mặc định dùng UUID v4, chuyển sang v7 cho khóa chính, chỉ chọn ULID hoặc NanoID khi cần dạng chữ, giữ chỉ mục duy nhất, lưu thời gian thật trong cột riêng, và đừng bao giờ coi một mã định danh là bí mật.