B+ tree là gì: Cấu trúc dữ liệu chuẩn cho cơ sở dữ liệu và ổ cứng

B+ tree là gì? Đây là cấu trúc dữ liệu cây cân bằng tự thân, được dùng làm chỉ mục (index) mặc định trong hầu hết cơ sở dữ liệu quan hệ và hệ thống tệp. B+ tree mở rộng khái niệm cây nhị phân tìm kiếm (binary search tree) bằng cách cho phép mỗi nút có nhiều hơn hai nút con, từ đó giảm chiều cao cây xuống mức tối thiểu. Bài viết này giải thích nguyên lý hoạt động, lý do B+ tree thắng trong bối cảnh ổ cứng, và cách bạn dùng nó trong SQL thực tế.

Sơ đồ tổ chức một cây B+ tree với các nút lá chứa khoá nối tiếp bằng con trỏ

B+ tree là gì

B+ tree là một loại cây tìm kiếm cân bằng tự động, giữ dữ liệu luôn ở trạng thái sắp xếp và cho phép tìm kiếm, duyệt tuần tự, chèn và xoá trong thời gian logarithmic. Ý tưởng gốc do Rudolf Bayer và Edward McCreight đề xuất khi làm việc tại phòng thí nghiệm Boeing, nhằm quản lý các trang chỉ mục của những tệp truy cập ngẫu nhiên có dung lượng rất lớn. Bài báo gốc mang tên Organization and maintenance of large ordered indices, lưu hành nội bộ năm 1970 rồi công bố trên tạp chí Acta Informatica.

Khác biệt cốt lõi giữa B+ tree và B-tree cổ điển nằm ở chỗ: trong B+ tree, toàn bộ khóa (key) và dữ liệu đều nằm ở tầng lá, còn các nút nội bộ chỉ giữ khóa dùng làm mốc phân chia. Nhờ vậy các nút lá được nối thành một danh sách liên kết, nên quét toàn bộ bảng chỉ tốn thời gian tuyến tính và không phải quay lui trên cây. Đây là lý do B+ tree được chọn cho phần lớn thao tác quét bảng trong SQL.

Vì sao B+ tree phù hợp với dữ liệu lưu trên đĩa

Ổ cứng và SSD không tối ưu cho việc đọc từng byte lẻ. Chúng tối ưu cho việc đọc theo khối (page hoặc block) có dung lượng vài kilobyte. B+ tree được thiết kế đúng cho mô hình này: một nút thường được chọn kích thước vừa đúng bằng một khối đĩa, nên mỗi lần đọc đĩa là một lần nạp nguyên một nút chứa hàng chục tới hàng trăm khóa.

Chi phí thực tế của một lần tìm kiếm không phải là số phép so sánh, mà là số lần phải ra đĩa. Mỗi lần ra đĩa tốn hàng microsecond, trong khi so sánh trong bộ nhớ tốn vài nanosecond. Chính vì vậy B+ tree cố gắng giữ cây thấp: với khoá khoảng 20 và mỗi nút chứa 100 khóa, chỉ cần hai tầng là đủ bao phủ 10.000 bản ghi.

Đặc điểm Cây nhị phân tìm kiếm B+ tree
Số nhánh tối đa mỗi nút 2 Hàng chục tới hàng trăm
Chiều cao cây Log2(n), cao Logm(n), thấp
Số lần đọc đĩa cho một lần tìm Nhiều Rất ít
Quét tuần tự toàn bộ cây Phải quét lại từ gốc Đi thẳng qua danh sách lá
Dữ liệu nằm ở đâu Mọi nút Chỉ ở tầng lá
Minh hoạ thao tác xoá một khoá trong cây B+ tree làm các nút lá dồn lại

Cấu trúc và tính chất cân bằng

Theo định nghĩa của Knuth, một cây B+ tree bậc m thỏa các tính chất: mỗi nút có nhiều nhất m nút con; mọi nút trừ nút gốc và tầng lá có ít nhất ceil(m/2) nút con; nút gốc có ít nhất hai nút con trừ khi nó vốn là lá; và toàn bộ tầng lá nằm trên cùng một mức. Một nút không phải lá có k nút con thì chứa k trừ 1 khóa, và các khóa đó làm ranh giới cho các cây con: mọi giá trị bên trái khóa đầu nhỏ hơn khóa đó, giá trị giữa hai khóa nằm trong khoảng, và giá trị bên phải lớn hơn.

Ràng buộc trên làm cây tự cân bằng theo cơ chế. Khi một nút tràn, nó tách thành hai nút hợp lệ và đẩy một khóa lên nút cha. Ngược lại, khi một nút thiếu chỗ sau khi xoá, nó được gộp với nút anh em và mượn một khóa từ nút cha. Vì mọi nút luôn có tối thiểu một nửa chỗ trống nên cây không bao giờ suy biến thành một danh sách tuyến tính, kể cả khi dữ liệu được chèn hoàn toàn theo thứ tự tăng dần, trường hợp kinh điển làm cây nhị phân nhị phân suy biến.

Chi phí của mỗi thao tác chèn hoặc xoá là O(log n) về số nút phải đi qua, kèm một số lần tách hoặc gộp không đổi lượng. Một cây B+ tree bậc m giữ n khoá có chiều cao khoảng logm(n), và mỗi tầng thêm một tầng giúp chứa gấp m lần số phần tử so với tầng trước.

Các thao tác chính

Tìm kiếm điểm. Bắt đầu từ nút gốc, so sánh khóa cần tìm với các mốc trong nút để chọn nhánh con phù hợp, lặp lại cho tới tầng lá. Vì mọi tầng lá cùng độ sâu nên số bước đi luôn bằng chiều cao cây.

Quét theo khoảng. Tìm điểm bắt đầu như trên, sau đó đi theo danh sách liên kết giữa các nút lá. Với một bảng lớn, đây là con đường nhanh nhất và là lý do lệnh quét bảng có thể dùng index một cách hiệu quả.

Chèn. Đi tới lá đích. Nếu lá còn chỗ, chèn khóa tại đúng vị trí để giữ thứ tự. Nếu đầy, tách lá thành hai, phân bổ lại khóa và đẩy khóa phân cách lên tầng trên, lặp lại nếu nút cha cũng tràn.

Xoá. Xoá ở tầng lá. Nếu nút lá rơi xuống dưới ngưỡng tối thiểu, gộp với nút anh em kề bên và điều chỉnh con trỏ ở nút cha.

Cấu trúc cây B+ tree với các khoá ở tầng nội bộ và tầng lá

B+ tree trong thực tế với cơ sở dữ liệu

PostgreSQL dùng B-tree làm kiểu chỉ mục mặc định, mở rộng thêm so với mô hình gốc. MySQL và MariaDB cũng chọn B+ tree cho InnoDB. Những gì cơ sở dữ liệu thêm vào gồm chỉ mục không duy nhất, chỉ mục toàn cục, kiểu chỉ mục trên biểu thức và khả năng tìm kiếm theo khoảng thay vì chỉ tiền tố.

Hệ quả thực hành rất quan trọng: một chỉ mục B+ tree trên cột email giúp tìm nhanh khi truy vấn có điều kiện bằng, nhưng gần như vô dụng với điều kiện không bằng như LIKE '%abc' hay một hàm bao quanh cột như LOWER(email), vì khi đó thứ tự trong chỉ mục không còn khớp với thứ tự giá trị thực. Muốn dùng được, bạn phải tạo chỉ mục trên biểu thức đúng với biểu thức truy vấn.

Bạn có thể xem chiều cao cây chỉ mục của mình bằng cách xem từ khoá btree trong kết quả EXPLAIN. Con số này cho biết cấp độ của cây, và nó là chỉ báo trực tiếp cho việc truy vấn có đang phải ra đĩa nhiều lần hay không. Chỉ số thường từ 3 trở xuống là ổn với bảng trên đĩa.

Một lưu ý về thiết kế: chỉ mục B+ tree không miễn phí. Mỗi lần chèn, xoá hoặc cập nhật đều phải ghi lại các nút trên đường đi, và những nút đó có thể không còn đủ chỗ, dẫn tới tách trang và phân mảnh. Cũng vì vậy một bảng có nhiều chỉ mục sẽ chậm ghi kể cả tìm nhanh hơn. Chọn chỉ mục luôn bắt đầu từ câu hỏi truy vấn nào thực sự cần nó, không phải từ mọi cột đều có thể lọc.

Chi tiết về mô hình B-tree và lịch sử phát triển được ghi trong bài B-tree trên Wikipedia. Nếu muốn đi sâu vào phần chỉ mục của PostgreSQL, hãy xem tài liệu chính thức B-tree của PostgreSQL, phần này mô tả rõ cách tách trang, cách dùng chỉ mục một phần và cách bảo đảm VACUUM hoạt động đúng trên cấu trúc này.

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Trình biên dịch là gì: Quy trình biên dịch mã nguồn từ A-Z

Trình biên dịch là gì: Từ mã nguồn đến chương trình chạy được Trình biên dịch (compiler) là chương trình dịch mã nguồn viết bằng ngôn ngữ cấp cao —…

Xem thêm

Event Sourcing là gì – Nguyên lý và Ứng dụng thực tế

Event Sourcing là một kiến trúc phần mềm lưu toàn bộ thay đổi trạng thái ứng dụng dưới dạng một chuỗi sự kiện bất biến, thay vì chỉ giữ lại…

Xem thêm

Bloom filter là gì? Cấu trúc dữ liệu xác suất tiết kiệm bộ nhớ

Bloom filter là gì? Đây là một cấu trúc dữ liệu xác định xác suất, được nhà khoa học máy tính Burton Howard Bloom đề xuất năm 1970, dùng để…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất