Những cạm bẫy khi sử dụng UUID làm khóa chính trong SQLite
Việc sử dụng UUID ngẫu nhiên (UUID4) làm khóa chính trong SQLite có thể gây ra sự suy giảm hiệu suất nghiêm trọng, chậm hơn gấp 10-12 lần so với khóa số nguyên. Nguyên nhân là do tính chất ngẫu nhiên khiến cơ sở dữ liệu phải liên tục cân bằng lại cấu trúc B-tree. Bài viết đề xuất sử dụng UUID có thứ tự thời gian (UUID7) để giải quyết vấn đề này.

Việc sử dụng UUID ngẫu nhiên làm khóa chính trong cơ sở dữ liệu là một thực tế phổ biến. Tuy nhiên, một nhược điểm lớn của UUID ngẫu nhiên (đặc biệt là UUID4) là tính chất không có thứ tự của chúng, gây ra nhiều thao tác phân trang (paging) thừa thãi cho chỉ mục nhóm (clustered index). Bài viết này sẽ giúp chúng ta hiểu rõ hơn về chi phí hiệu năng của việc này trong SQLite.
Mặc dù bài viết tập trung vào SQLite, nhưng vấn đề của UUID ngẫu nhiên cũng mở rộng sang các cơ sở dữ liệu khác sử dụng chỉ mục nhóm.
Chỉ mục nhóm (Clustered Index) là gì?
Chỉ mục nhóm xác định thứ tự lưu trữ vật lý của các hàng trong bảng. Dữ liệu của bảng được lưu trữ trong các trang lá của chỉ mục, được sắp xếp theo khóa chỉ mục. Vì vậy:
- Mỗi bảng chỉ có thể có một chỉ mục nhóm (các hàng chỉ có thể được sắp xếp vật lý theo một cách).
- Chỉ mục nhóm chính là bảng. Các nút lá chứa dữ liệu hàng đầy đủ.
Ngược lại, chỉ mục không nhóm (non-clustered index) chỉ lưu trữ các cột được lập chỉ mục cùng với con trỏ đến dữ liệu hàng thực tế, dữ liệu này nằm ở nơi khác.
Rowid và WITHOUT ROWID trong SQLite
Mọi bảng SQLite thông thường đều có một khóa chính nguyên 64-bit ngầm định gọi là rowid. Dữ liệu của bảng được lưu trữ trong một cây B (B-tree) được sắp xếp theo rowid. Đây thực chất là chỉ mục nhóm của SQLite. Thứ tự lưu trữ vật lý của các hàng tuân theo chuỗi rowid.
SQLite cũng hỗ trợ các bảng WITHOUT ROWID. Các bảng này không có rowid ngầm định. Thay vào đó, khóa chính mà bạn khai báo sẽ trở thành chỉ mục nhóm.
Đánh giá hiệu năng: INT so với UUID4
Để thiết lập đường cơ sở, chúng ta chèn 10 triệu hàng dữ liệu theo lô 1 triệu hàng bằng khóa chính INT thông thường. Kết quả cho thấy tốc độ khoảng 1 triệu lượt chèn mỗi giây.
Tuy nhiên, khi chuyển sang sử dụng UUID4 ngẫu nhiên làm khóa chính trên bảng WITHOUT ROWID, kết quả gây sốc: hiệu suất chậm hơn 10-12 lần!
Biểu đồ diffgraph so sánh hiệu năng giữa INT và UUID4
Nguyên nhân và giải pháp với UUID7
Tại sao lại có sự khác biệt lớn như vậy? Chúng ta không cần đoán mà có thể xem hồ sơ hiệu năng (profile). Từ biểu đồ diffgraph, có thể thấy rằng chúng ta đang dành nhiều thời gian hơn để cân bằng cây, đọc và ghi. Điều này là do tính chất không có thứ tự của UUID4 khiến chúng được sắp xếp ngẫu nhiên, buộc SQLite phải liên tục cân bằng lại cây B.
Chúng ta có thể khắc phục vấn đề này về mặt lý thuyết bằng cách sử dụng UUID7. UUID7 được thiết kế để có thứ tự theo thời gian, loại bỏ vấn đề sắp xếp ngẫu nhiên của UUID4.
Khi thử nghiệm với UUID7, hiệu suất quay trở lại mức hợp lý, chỉ chậm hơn một chút so với đường cơ bản INT. Sự chậm chạp nhỏ này là do kích thước của khóa UUID blob (16 byte) lớn hơn khóa INT (8 byte).
Kết luận
Hy vọng bài viết này giúp minh họa một số cạm bẫy khi sử dụng khóa chính UUID trong SQLite và cách để điều hướng chúng. Nếu cần sử dụng UUID, hãy cân nhắc kỹ các phiên bản có thứ tự như UUID7 để tránh làm giảm hiệu suất cơ sở dữ liệu của bạn.
Bài viết liên quan

Công nghệ
CEO Palantir: 10% thế giới "ghét chúng tôi một cách chuyên nghiệp"
05 tháng 5, 2026

Phần mềm
Fine-tuning LLM: Khi trí tuệ nhân tạo viết tài liệu kỹ thuật theo phong cách thập niên 90
05 tháng 6, 2026

Phần mềm
Google ra mắt Rambler: Tính năng chép lời AI trên Gboard có thể gây áp lực lớn cho các startup
12 tháng 5, 2026
