0

Clustered Index Là Gì? Đừng Chọn "Bừa" Primary Key Nếu Không Muốn Database Bị Bóp Hiệu Năng

Chào anh em Viblo! 👋

Trong các bài viết trước, chúng ta đã nói rất nhiều về cách dùng Index sao cho đúng qua các quy tắc như Left-most Prefix hay Equality Before Range. Nhưng có một sự thật bất ngờ là: Ngay cả khi bạn chưa chủ động tạo bất kỳ một cái Index nào, Database của bạn thường đã tự động tạo sẵn một loại Index vô cùng đặc biệt rồi. Đó chính là Clustered Index (Index cụm).

Hồi mới làm dự án, mình cứ nghĩ đơn giản: "Thì cứ gắn cái PRIMARY KEY vào cho mỗi bảng là xong, có gì đâu mà phải nghĩ?". Cho đến khi hệ thống phình to lên hàng chục triệu bản ghi, tốc độ INSERT bắt đầu chậm đi một cách khó hiểu, ổ đĩa thì liên tục báo quá tải I/O. Sau một hồi "đào bới", mình mới ngộ ra thủ phạm chính là cách mình chọn Primary Key đã làm tổn thương cái Clustered Index bên dưới.

Hôm nay, mình sẽ cùng anh em bóc tách xem Clustered Index hoạt động như thế nào và kinh nghiệm xương máu khi làm việc với nó nhé!

1. Clustered Index là gì? Hãy tưởng tượng một cuốn Từ Điển 📕

Để hiểu Clustered Index, cách dễ nhất là mở một cuốn từ điển tiếng Anh.

Trong cuốn từ điển, các từ vựng được sắp xếp theo thứ tự chữ cái từ A đến Z. Và quan trọng nhất là gì? Nội dung định nghĩa của từ đó nằm ngay tại vị trí của từ đó luôn. Bạn tìm đến chữ "Cat", bạn thấy ngay nghĩa của từ "Cat" ở đó mà không cần phải lật đi đâu nữa.

Trong Database, Clustered Index chính là cuốn từ điển đó.

  • Khi một bảng có Clustered Index, vị trí vật lý của dữ liệu trên ổ cứng sẽ được sắp xếp theo đúng thứ tự của Index đó.
  • Nút lá (Leaf Nodes) của cấu trúc cây Clustered Index chính là các dòng dữ liệu thực tế (Actual Data Rows), chứ không phải là một con trỏ (pointer) trỏ đi nơi khác.

Quy tắc tối thượng: Vì dữ liệu trên ổ cứng chỉ có thể xếp theo một thứ tự vật lý duy nhất (bạn không thể vừa xếp cuốn từ điển theo bảng chữ cái, vừa xếp nó theo số lượng ký tự được), nên mỗi bảng chỉ có thể có DUY NHẤT MỘT Clustered Index.

Thông thường, khi bạn định nghĩa một cột là PRIMARY KEY, các Hệ quản trị CSDL (như MySQL InnoDB, SQL Server) sẽ tự động lấy cột đó để làm Clustered Index.

2. Sự khác biệt chí mạng với Non-Clustered Index

Để anh em không bị nhầm lẫn, hãy làm một so sánh nhanh với Non-Clustered Index (Index thông thường mà anh em hay tự tạo thêm):

Đặc điểm Clustered Index Non-Clustered Index
Số lượng Chỉ có 1 duy nhất trên mỗi bảng. Có thể có nhiều (hàng chục cái tùy ý).
Bản chất nút lá Chứa chính xác dòng dữ liệu thực tế. Chỉ chứa giá trị của cột đó + con trỏ (hoặc Primary Key) trỏ về dòng dữ liệu gốc.
Tốc độ Tìm kiếm Cực kỳ nhanh (Tìm thấy Index là có luôn data, không tốn thêm bước Key Lookup). Chậm hơn một chút nếu phải thực hiện Key Lookup để lấy các cột khác ngoài Index.

3. Kinh nghiệm thực chiến: Cái bẫy mang tên "Random UUID"

Đây là nơi mà rất nhiều anh em Dev (bao gồm cả mình ngày trước) dính chưởng.

Xu hướng hiện đại là anh em rất thích dùng UUID (v4) làm Primary Key thay cho số tự tăng (Auto-Increment INT/BIGINT) để bảo mật (tránh bị user dò đoán ID) và dễ scale khi làm hệ thống phân tán (Distributed System).

Tuy nhiên, UUID v4 là một chuỗi ngẫu nhiên hoàn toàn (Random). Và đây là thảm họa đối với Clustered Index:

  1. Cơn ác mộng Page Splits (Phân rã trang): Vì dữ liệu của Clustered Index bắt buộc phải xếp liên tục theo thứ tự trên ổ cứng. Nếu bạn dùng ID tự tăng (1, 2, 3, 4...), DB cứ việc xếp các dòng dữ liệu mới tinh vào cuối hàng một cách mượt mà.
  2. Nhưng nếu bạn dùng UUID ngẫu nhiên, dòng dữ liệu thứ nhất có ID bắt đầu bằng chữ g, dòng thứ hai bắt đầu bằng chữ a. DB sẽ phải bới tung ổ cứng lên, chen ngang dòng chữ a vào trước dòng chữ g.
  3. Khi các trang dữ liệu (Pages) trên ổ cứng bị lấp đầy, việc chen ngang này sẽ ép Database phải chia đôi trang cũ ra để nhét dữ liệu mới vào (Page Split). Việc này ngốn một lượng tài nguyên CPU và I/O đĩa khổng lồ, khiến tốc độ INSERT của hệ thống tụt dốc không phanh khi dữ liệu lớn dần.

Giải pháp khắc phục là gì?

Cách 1: Nếu không bắt buộc, hãy trung thành với BIGINT Auto-Increment làm Primary Key (Clustered Index), vừa nhẹ vừa tăng tiến tuần tự. Cách 2: Nếu bắt buộc phải dùng UUID ở tầng Application để làm việc với client, hãy giữ một cột id BIGINT AUTO_INCREMENT làm Primary Key vật lý cho DB, còn cột uuid VARCHAR/BINARY thì chỉ làm Unique Key (Non-Clustered Index) để query. Cách 3 (Thời thượng nhất): Sử dụng UUID v7 hoặc Snowflake ID. Đây là các loại UUID thế hệ mới có chứa yếu tố thời gian (Timestamp) nằm ở các ký tự đầu tiên. Nhờ vậy, chúng vừa đảm bảo tính duy nhất, không trùng lặp, lại vừa sắp xếp tăng dần theo thời gian, giúp Clustered Index không bị hiện tượng chen ngang gây nát ổ cứng.

Đúc kết lại

Clustered Index là xương sống quyết định cách dữ liệu của bạn được tổ chức vật lý dưới ổ đĩa. Thiết kế nó tốt, hệ thống của bạn sẽ chạy êm ru. Thiết kế nó sai, bạn sẽ phải trả giá bằng tài nguyên server đắt đỏ.

Hãy luôn nhớ: Luôn chọn Clustered Index (Primary Key) là một cột có tính chất tăng dần theo thời gian và có kích thước càng nhỏ gọn càng tốt.

Hi vọng bài viết này mang lại góc nhìn thực tế cho anh em khi đặt bút thiết kế bảng dữ liệu tiếp theo. Chúc anh em tối ưu hệ thống thành công! Happy Coding! 🚀💻


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí