Những cạm bẫy khi sử dụng UUID làm khóa chính trong SQLite

Phần mềm05 tháng 6, 2026·3 phút đọc

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.

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 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à UUID4Biể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.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗