UUID v4 vs v7 — Khác Biệt Và Khi Nào Dùng Mỗi Cái
UUID v4 đã là lựa chọn mặc định cho các khóa chính phân tán trong hơn một thập kỷ. Tạo một giá trị 128-bit ngẫu nhiên, định dạng nó thành tám nhóm hex, xong. Không yêu cầu phối hợp, xác suất va chạm thực tế bằng không, hoạt động ở mọi nơi. Vì vậy tại sao UUID v7 — được chuẩn hóa trong RFC 9562 năm 2024 — đang lan rộng nhanh qua các hệ thống production năm 2026?
Câu trả lời ngắn: hiệu năng cơ sở dữ liệu. UUID v4 phá hủy cục bộ chỉ mục B-tree. UUID v7 khắc phục điều đó trong khi giữ mọi thứ khác mà các nhà phát triển yêu thích về UUID. Bài viết này giải thích mỗi phiên bản thực sự là gì, tại sao sự khác biệt quan trọng ở quy mô, khi nào chọn mỗi cái, và cách di chuyển mà không có downtime.
Cần tạo UUID ngay bây giờ? Thử Toova UUID generator, hỗ trợ cả v4 và v7 hàng loạt. Đối với các định danh ngẫu nhiên khác, trình tạo chuỗi ngẫu nhiên và trình tạo mật khẩu bao quát các token ngắn hơn.
UUID v4 — Ngẫu Nhiên Thuần Túy
UUID phiên bản 4 dùng một bộ tạo số giả ngẫu nhiên an toàn về mật mã (CSPRNG) để lấp đầy 122 trong 128 bit. Sáu bit còn lại cố định: bốn bit mã hóa phiên bản (0100) và hai bit mã hóa biến thể (10). Kết quả trông như thế này:
f47ac10b-58cc-4372-a567-0e02b2c3d479
^^^^
phiên bản = 4 (ngẫu nhiên) Tính ngẫu nhiên là tính năng. Hai hệ thống độc lập có thể tạo các UUID mà không có phối hợp và không bao giờ va chạm — xác suất của một va chạm trong một bộ 2.71 triệu tỷ UUID v4 là khoảng 50%, có nghĩa là đối với bất kỳ ứng dụng thực tế nào rủi ro là không đáng kể. Bạn không cần một máy chủ ID tập trung, một chuỗi cơ sở dữ liệu hoặc một khóa phân tán.
Cách v4 được tạo
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
Thư viện uuid (JavaScript), uuid.uuid4() của Python, google/uuid của Go và mọi runtime ngôn ngữ chính ủy thác cho CSPRNG của OS — /dev/urandom trên Linux, CryptGenRandom trên Windows. Chi phí tạo thực tế bằng không.
Vấn đề: chèn ngẫu nhiên giết các chỉ mục B-tree
Một chỉ mục B-tree giữ dữ liệu được sắp xếp. Khi bạn chèn một hàng mới, cơ sở dữ liệu tìm nơi khóa mới phù hợp trong thứ tự đã sắp xếp và đặt nó ở đó. Nếu mọi khóa mới là ngẫu nhiên, nó rơi vào một vị trí ngẫu nhiên trong chỉ mục — có nghĩa là mọi chèn cần tải một trang khác từ đĩa vào buffer pool. Ở khối lượng thấp, điều này không thể nhìn thấy. Ở khối lượng cao (hàng triệu hàng, tỷ lệ INSERT cao), nó tạo ra một mẫu được gọi là phân mảnh chỉ mục: chỉ mục lấp đầy với các trang nửa rỗng vì mỗi chèn nhấn một vị trí khác, và buffer pool churns liên tục vì tập làm việc nóng kéo dài toàn bộ chỉ mục thay vì một lát cắt gần đây có thể đoán trước.
Các triệu chứng trong production: độ trễ INSERT tăng, chi phí autovacuum tăng (PostgreSQL), áp lực checkpoint tăng, và hiệu năng đọc cho các bản ghi gần đây suy giảm vì "gần đây" không còn ánh xạ đến bất kỳ cục bộ nào trong chỉ mục. Đây không phải giả thuyết — đó là một điểm đau được tài liệu hóa tốt trên các bảng PostgreSQL và MySQL lớn với các khóa chính UUID v4.
UUID v7 — Ngẫu Nhiên Được Sắp Xếp Theo Thời Gian
UUID v7 được thiết kế rõ ràng để giải quyết vấn đề phân mảnh B-tree. Nó mã hóa một dấu thời gian Unix mili giây 48-bit trong các bit có ý nghĩa nhất, theo sau bởi các bit phiên bản, 12 bit ngẫu nhiên, các bit biến thể và 62 bit ngẫu nhiên khác. Tổng tính ngẫu nhiên: 74 bit — vẫn vượt xa cái cần thiết để ngăn ngừa va chạm.
018f4e6b-a23c-7d45-9abc-0e02b2c3d479
^^^^^^^^^^^^^^
Dấu thời gian Unix 48-bit (độ chính xác mili giây)
^
phiên bản = 7 Vì dấu thời gian chiếm các bit cao, các UUID được tạo sau sắp xếp sau các UUID được tạo sớm hơn. Các chèn luôn được thêm vào cạnh bên phải của chỉ mục. Cơ sở dữ liệu chỉ cần giữ trang chỉ mục gần đây nhất nóng trong buffer pool, không phải toàn bộ chỉ mục. Ở tỷ lệ INSERT cao, điều này một mình có thể cắt độ trễ ghi 30–60% và giảm I/O một bậc độ trên các bảng với hàng chục triệu hàng.
Cách v7 được tạo
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// => '018f4e6b-a23c-7d45-9abc-0e02b2c3d479'
Cùng thư viện uuid đã thêm hỗ trợ v7 trong phiên bản 10. Thư viện chuẩn của Python đã thêm nó trong 3.14. PostgreSQL 17 bao gồm uuidv7() như một hàm tích hợp. Nếu bạn đang ở một stack cũ hơn, một số thư viện nhỏ cung cấp tạo v7 không có dependency.
Tính đơn điệu dưới mili giây
Điều gì xảy ra khi hai UUID v7 được tạo trong cùng một mili giây? RFC 9562 cho phép các triển khai tăng một bộ đếm đơn điệu trong các bit ngẫu nhiên để đảm bảo thứ tự trong cùng một mili giây. Thư viện uuid làm điều này theo mặc định. Kết quả: ngay cả khi 10.000 ID được tạo trong một mili giây, chúng vẫn sắp xếp đúng.
Vấn Đề Phân Mảnh Chỉ Mục B-Tree Chi Tiết
Để hiểu tại sao điều này quan trọng, hãy cân nhắc điều gì xảy ra ở 10.000 chèn mỗi giây với một khóa chính UUID v4 trên một bảng với 100 triệu hàng hiện có:
- Mỗi chèn tạo một khóa 128-bit ngẫu nhiên rơi vào một vị trí ngẫu nhiên giữa 100 triệu mục hiện có.
- Cơ sở dữ liệu phải tải trang B-tree cụ thể chứa vị trí đó vào buffer pool.
- Với 100 triệu hàng và các trang 8 KB, chỉ mục kéo dài khoảng 100.000 trang. Mỗi giây, 10.000 trang khác nhau cần thiết — vượt xa cái một shared_buffers điển hình 8 GB có thể giữ chỉ cho chỉ mục này.
- Mỗi cache miss dẫn đến một đọc đĩa. Ở 10.000 chèn/giây, điều này có thể tạo hàng nghìn đọc đĩa ngẫu nhiên mỗi giây, làm bão hòa ngay cả SSD trên các bảng lớn.
Với UUID v7, tất cả 10.000 chèn mỗi giây rơi vào trang lá ngoài cùng bên phải (hoặc một số ít trang gần đây). Buffer pool chỉ cần giữ những trang đó nóng. Tỷ lệ hit cache cho các ghi tiếp cận 100%. Khuếch đại ghi giảm đáng kể.
Lợi ích tương tự áp dụng cho quét phạm vi: WHERE created_at BETWEEN x AND y trên một bảng v4 yêu cầu một quét chỉ mục đầy đủ hoặc một chỉ mục dấu thời gian riêng biệt. Trên một bảng v7, chính khóa chính là chỉ mục dấu thời gian — truy vấn có thể tìm kiếm trực tiếp đến phạm vi đúng.
Cách Dùng UUID v7 Trong PostgreSQL
-- Hoạt động nguyên bản với kiểu UUID trong PostgreSQL 16+
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (ngẫu nhiên)
-- Chuyển sang UUIDv7 qua extension hoặc tạo phía ứng dụng
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Với UUIDv7: dấu thời gian đã được nhúng, vì vậy
-- cột created_at riêng biệt này thường là dư thừa.
PostgreSQL 17 ship uuidv7() nguyên bản. Đối với PostgreSQL 14–16, extension pg_uuidv7 cung cấp cùng hàm. Kiểu dữ liệu UUID lưu trữ cả v4 và v7 giống hệt nhau — 16 byte, không chi phí. Sự khác biệt duy nhất là mẫu bit xác định thứ tự sắp xếp.
Một hệ quả hữu ích: vì dấu thời gian được nhúng, nhiều bảng không còn cần một cột created_at riêng biệt để sắp xếp hoặc hiển thị. Bạn có thể trích xuất dấu thời gian từ một UUID v7 với một cuộc gọi hàm duy nhất. Điều này giảm độ phức tạp schema và loại bỏ một ghi mỗi chèn.
UUID v4 vs v7 — Các Đánh Đổi
| Thuộc tính | UUID v4 | UUID v7 |
|---|---|---|
| Bit ngẫu nhiên | 122 | 74 |
| Thứ tự sắp xếp | Ngẫu nhiên | Theo thời gian |
| Hiệu năng INSERT B-tree | Kém (phân mảnh) | Xuất sắc (tuần tự) |
| Rò rỉ dấu thời gian | Không | Có (độ chính xác ms) |
| Tiêu chuẩn RFC | RFC 4122 (2005), RFC 9562 (2024) | RFC 9562 (2024) |
| Hỗ trợ thư viện | Phổ quát | Phát triển nhanh (2024–2026) |
| created_at được nhúng | Không | Có |
Hướng Dẫn Di Chuyển — v4 Sang v7
Di chuyển một bảng hiện có từ các khóa chính UUID v4 sang v7 là một thao tác đa bước. Ràng buộc chính: bạn không thể thay đổi giá trị của một khóa chính được tham chiếu bởi các khóa ngoại mà không cập nhật tất cả các bảng tham chiếu. Lập kế hoạch cho một cửa sổ bảo trì hoặc dùng cách tiếp cận ghi kép.
-- 1. Thêm cột mới
ALTER TABLE orders ADD COLUMN id_v7 UUID;
-- 2. Backfill các hàng hiện có (bảo toàn created_at gốc làm nguồn
-- dấu thời gian; dùng một hàm thư viện UUIDv7 trong ứng dụng của bạn)
UPDATE orders SET id_v7 = generate_uuidv7(created_at);
-- 3. Xác minh không còn NULL
SELECT COUNT(*) FROM orders WHERE id_v7 IS NULL;
-- 4. Hoán đổi các cột (yêu cầu cửa sổ bảo trì ngắn)
ALTER TABLE orders ALTER COLUMN id_v7 SET NOT NULL;
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT generate_uuidv7(now());
ALTER TABLE orders RENAME COLUMN id TO id_v4_old;
ALTER TABLE orders RENAME COLUMN id_v7 TO id;
ALTER TABLE orders ADD PRIMARY KEY (id); Đối với các di chuyển không có downtime trên các bảng lưu lượng cao, cách tiếp cận được khuyến nghị là:
- Thêm cột
id_v7mới với một giá trị mặc định máy chủ tạo các UUID v7 đi tiếp. - Backfill các hàng cũ theo các batch trong các giai đoạn lưu lượng thấp, dùng dấu thời gian
created_athiện có làm hạt giống cho phần dấu thời gian v7. - Cập nhật tất cả các cột khóa ngoại trong các bảng tham chiếu để trỏ đến
id_v7. - Đổi tên các cột và bỏ ràng buộc khóa chính cũ trong một cửa sổ bảo trì ngắn.
Bước backfill là dài nhất. Ở 10.000 hàng mỗi batch với một sleep 50 ms giữa các batch, một bảng 100 triệu hàng mất khoảng 8 giờ. Bắt đầu sớm.
Tác Động Thực Tế
Một số đội ngũ kỹ thuật đã xuất bản các benchmark so sánh UUID v4 và v7 trên các tập dữ liệu có kích thước production. Các phát hiện nhất quán:
- Thông lượng INSERT: Cải thiện 2–5 lần trên các bảng với 50M+ hàng khi chuyển từ v4 sang v7, với lợi ích tăng khi kích thước bảng phát triển.
- Độ trễ ghi p99: Giảm từ hàng trăm mili giây (v4, dưới tải) xuống mili giây đơn (v7) trên cùng phần cứng.
- Kích thước chỉ mục: Các chỉ mục B-tree trên các cột v7 nhỏ hơn 15–30% so với các chỉ mục v4 tương đương trên cùng dữ liệu vì phân mảnh để lại ít trang nửa rỗng hơn.
- Hiệu quả buffer pool: Tỷ lệ hit của shared buffers cho chỉ mục khóa chính đi từ ~40% (v4, bảng lớn) đến ~99% (v7), vì chỉ các trang gần đây cần giữ nóng.
Các lợi ích không đáng kể dưới khoảng 1 triệu hàng. Nếu bảng của bạn vẫn nhỏ, giữ với v4 để đơn giản. Trên 10 triệu hàng với bất kỳ tỷ lệ INSERT có ý nghĩa nào, v7 là mặc định tốt hơn.
Khi Nào Dùng Mỗi Cái
Dùng UUID v7 khi:
- Bạn đang thiết kế một schema mới và bảng sẽ phát triển lớn (10M+ hàng).
- Bảng có tỷ lệ INSERT cao — sự kiện, log, đơn hàng, tin nhắn, thông báo.
- Bạn muốn một khóa chính kiêm dấu thời gian tạo (loại bỏ một cột).
- Bạn đang dùng PostgreSQL 17, MySQL 8.0+, hoặc một ORM hiện đại hỗ trợ tạo v7.
- Sắp xếp theo ID tương đương về mặt ngữ nghĩa với sắp xếp theo thời gian tạo — điều này sẽ đúng cho hầu hết các bảng nặng append.
Dùng UUID v4 khi:
- Định danh được tiết lộ cho người dùng và không được tiết lộ thời gian tạo (các mã mời, các liên kết chia sẻ, các handle thanh toán).
- Bảng nhỏ và ổn định — không có lợi ích hiệu năng nào biện minh cho chi phí di chuyển.
- Bạn đang tạo các ID trong một ngữ cảnh nơi dấu thời gian tạo nhạy cảm (các bản ghi người dùng riêng tư trong một sản phẩm cạnh tranh về các quang học tăng trưởng).
- Bạn cần một thứ gì đó làm một thông tin xác thực hoặc token một lần — dùng một bộ tạo bí mật chuyên dụng, không phải bất kỳ phiên bản UUID nào.
Tóm Lược
UUID v4 ngẫu nhiên, riêng tư và được hỗ trợ phổ quát. UUID v7 được sắp xếp theo thời gian, thân thiện với cơ sở dữ liệu và giờ là tiêu chuẩn trong các thư viện và cơ sở dữ liệu chính. Đối với các schema mới với các bảng lớn hoặc phát triển nhanh, v7 là mặc định tốt hơn. Đối với các ID được tiết lộ cho người dùng nơi rò rỉ dấu thời gian là một mối lo, v4 vẫn là lựa chọn đúng.
Tạo cả hai phiên bản ngay lập tức với Toova UUID generator — không yêu cầu tài khoản. Đối với các token ngắn hơn, trình tạo chuỗi ngẫu nhiên tạo các ID chữ và số ở bất kỳ độ dài nào.