SydexaSydexa

SQL Nâng Cao

Index và B+TreeCái giá của IndexEquality & EXPLAIN PlanComposite Index & Thứ tự cột3 chiến thuật ScanKhi Function phá IndexPlan Caching & SQL InjectionRange, LIKE & Bitmap CombinePartial Index & Bẫy NULLJoin - Nested Loop & IndexJoin - Hash Join & Sort MergeClustering Data - Index Filter & Index Only ScanClustering Data - Index Organized TablesSorting & Grouping với IndexPartial Result, Top N, Pagination & Window FunctionsInsert, Update, Delete - tác động của Index lên DML
Trang chủKhoá họcSQL Nâng Cao
Index và B+Tree
Từ Full Table Scan đến B+Tree, hiểu tại sao disk I/O là nút thắt, cách xây dựng cấu trúc index từng bước, và sự khác biệt giữa B-Tree thuần và B+Tree mà database thực sự dùng.
0:00/0:00

Quick Quiz

1.Ở ví dụ mở đầu, chỉ thêm 1 dòng CREATE INDEX mà query nhanh hơn 4000 lần. Theo bạn, điều gì đã thay đổi?
1/1
1/1

Lộ trình bài học

Câu hỏi vừa rồi chạm đến bản chất của Index, một cấu trúc dữ liệu giúp database nhảy thẳng đến kết quả thay vì duyệt từng dòng. Bài học này sẽ đưa bạn từ bài toán gốc đến cấu trúc hoàn chỉnh, chia thành 3 chặng:

  • Chặng 1: Bài toán gốc - Ổ cứng chậm kinh khủng, Full Table Scan là ác mộng. Hiểu tại sao database CẦN một cấu trúc dữ liệu đặc biệt.
  • Chặng 2: Ý tưởng & thất bại - Index là gì, Binary Search Tree hoạt động ra sao, và tại sao nó thất bại trên ổ cứng.
  • Chặng 3: Xây dựng B+Tree - Leaf Node, Doubly Linked List, B-Tree, và cuối cùng B+Tree, cấu trúc dữ liệu đứng sau mọi database hiện đại.

Phần 1: Bài toán gốc: Ổ cứng chậm kinh khủng

0:00/0:00

Và có một chi tiết quan trọng nữa: khi ổ cứng đọc, nó không đọc một byte, nó đọc nguyên cả một khối (block), thường là 4KB hoặc 8KB hoặc 16KB. Giống như bạn mở tủ hồ sơ, bạn không lôi ra một tờ giấy, bạn lôi nguyên cả ngăn kéo.

Database cũng vậy, nó đọc dữ liệu theo đơn vị gọi là page (trang), mỗi page thường 4KB đến 16KB. Và đây chính là manh mối quan trọng nhất mà chúng ta sẽ cần để hiểu B-Tree.

Ghi nhớ 2 điều này, sẽ cần ở phần sau:

1. Đọc disk rất đắt → cần giảm số lần đọc

2. Mỗi lần đọc = đọc nguyên một page → cần tận dụng tối đa mỗi lần đọc

Quick Quiz

1.HDD chậm hơn RAM khoảng bao nhiêu lần?
1/1
1/1

Phần 2: Không có Index: Full Table Scan

0:00/0:00

Bây giờ đến lượt bạn cầm lái. Hãy chọn kích thước bảng và vị trí của dòng khớp, rồi cho hai làn chạy đua với nhau.

Trước khi bấm, hãy tự trả lời trong đầu một câu: nếu dòng cần tìm nằm ngay thứ 5 của bảng 10 triệu dòng, liệu Full Table Scan có nhanh không? Rồi chọn đúng phương án đó và xem điều gì thực sự xảy ra.

Chạy đua: Full Table Scan và Index

Bảng có bao nhiêu dòng
Dòng khớp nằm ở đâu
Không có indexFull Table Scan
0page đã đọc
“Còn dòng nào khớp nữa không? Mình đâu có biết…”
Chờ chạy
Có indexĐi qua Index
0page đã đọc
RootBranchLeafChờ chạy
Full Table Scan đọc
10.000 page
1.000.000 dòng ÷ 100 dòng mỗi page
Index đọc
4 page
3 tầng cây + 1 lần lấy dòng gốc
Chênh lệch
ít hơn 2.500 lần
bảng càng to, khoảng cách càng giãn

Thử chọn Ngay dòng thứ 5 rồi bấm chạy đua. Kết quả nằm gần như ở đầu bảng, vậy mà database vẫn phải bò hết 10.000 page. Lý do: cột này không có ràng buộc UNIQUE, nên sau khi tìm thấy dòng thứ 5, database vẫn không có cơ sở nào để khẳng định phía dưới không còn dòng nào khớp nữa. Nó buộc phải đi hết. Chỉ khi có UNIQUE, hoặc bạn tự viết LIMIT 1, nó mới được phép dừng sớm.

Quick Quiz

1.Khi chạy SELECT * FROM users WHERE email = 'abc@mail.com' mà KHÔNG có index, database phải làm gì?
1/1
1/1

Phần 3: Vậy Index là gì?

Tấm bản đồ chỉ đường

Hãy tưởng tượng bảng dữ liệu của bạn là một cuốn từ điển khổng lồ nhưng các trang lại bị xáo trộn ngẫu nhiên. Để tìm một từ, bạn buộc phải lật từng trang từ đầu đến cuối (Full Table Scan). Đó là ác mộng về hiệu năng.

Và đây là lúc Index xuất hiện như một “vị cứu tinh” mà bạn đã thấy ở animation phần trên.

Về bản chất, khi bạn chạy lệnh CREATE INDEX, database không sắp xếp lại bảng gốc. Thay vào đó, nó âm thầm tạo ra một cấu trúc dữ liệu riêng biệt, một bảng chỉ mục phụ trợ, chỉ chứa cột bạn cần tìm kiếm đã được sắp xếp trật tự, kèm theo một tấm vé chỉ đường (gọi là ROWID trong Oracle database hoặc ctid trong PostgreSQL) trỏ thẳng về dữ liệu gốc.

Hãy nghĩ về Index giống hệt phần Mục lục (Index) ở cuối một cuốn sách giáo khoa:

  • Nằm tách biệt - Nó nằm tách biệt ở những trang cuối (chiếm disk space riêng).
  • Tra cứu nhanh - Nó liệt kê từ khóa theo thứ tự A-Z để bạn tra cứu nhanh, kèm số trang (ROWID) để bạn lật ngay đến nơi.
  • Dư thừa có chủ đích - Nó là sự "Dư thừa có chủ đích": Nếu xé bỏ phần mục lục, nội dung sách không mất đi, nhưng tốc độ tìm kiếm của bạn sẽ giảm đi hàng nghìn lần.

Tuy nhiên, đừng để phép so sánh trên đánh lừa bạn rằng Index đơn giản.

Sách in là dữ liệu chết: Bạn chỉ làm mục lục một lần duy nhất khi xuất bản.

Database là cơ thể sống: Dữ liệu liên tục biến đổi.

Đây chính là điểm mà bạn cần lưu ý: Mỗi khi bạn INSERT, UPDATE, hay DELETE một dòng trong bảng, database buộc phải lao đi cập nhật lại toàn bộ các Index liên quan ngay lập tức. Bạn nhận được tốc độ đọc siêu nhanh, nhưng phải trả giá bằng công sức bảo trì hệ thống mỗi khi ghi dữ liệu.

Điều rút ra: Index là một cuộc đánh đổi: Chúng ta chấp nhận tốn thêm dung lượng lưu trữ (Disk Space) và giảm tốc độ ghi (Write Speed) để đổi lấy tốc độ đọc (Read Speed) cực nhanh.

Quick Quiz

1.Khi bạn tạo một Index, điều nào sau đây là ĐÚNG?
1/1
1/1

Chúng ta đã biết Index lưu gì: key đã sắp xếp + pointer về bảng gốc. Nhưng đó mới chỉ là một nửa câu chuyện. Nửa còn lại, cũng là nửa quyết định hiệu năng, là: dùng cấu trúc dữ liệu nào để tổ chức những key ấy trên disk?

Phần 4: Binary Search Tree: Và tại sao nó thất bại

Chúng ta cần một cấu trúc dữ liệu có thể tìm kiếm nhanh trong hàng triệu key đã sắp xếp. Nếu bạn từng học qua môn Cấu trúc dữ liệu & Giải thuật, câu trả lời đầu tiên hiện lên trong đầu gần như chắc chắn là Binary Search Tree (BST), cây nhị phân tìm kiếm. Chia đôi, so sánh, loại nửa còn lại, đơn giản và hiệu quả với độ phức tạp O(log₂n).

Trên lý thuyết, BST là ứng viên hoàn hảo. Nhưng khi đặt nó lên disk thay vì RAM, mọi thứ sụp đổ.

0:00/0:00

Quick Quiz

1.BST có O(log₂n) nhưng vẫn chậm trên disk. Tại sao?
1/1
1/1

Phần 5: Từ Sorted Array đến B-Tree: Bước tiến hóa

BST thất bại vì lãng phí 99.9% mỗi page. Ý tưởng đã rõ: nhét cho đầy mỗi page với data. Nhưng nhét bằng cách nào?

Phương án hiển nhiên: Sorted Array

Cách đơn giản nhất ai cũng nghĩ đến: xếp tất cả key vào một mảng đã sắp xếp trên disk. Phần đọc thì tuyệt vời, binary search, O(log n), nhanh gọn. Nhưng còn khi phải ghi thêm một key mới vào giữa thì sao?

Dự đoán trước khi xem

1.Mảng đã sắp xếp nằm trên disk: [5, 10, 15, 20, 25, 30, 35]. Bây giờ cần chèn giá trị 18 vào đúng vị trí của nó. Theo bạn database phải trả giá gì?
1/1
1/1

Sorted Array, Chi phí Insert O(n)

[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
5
10
15
20
25
30
35
18

Bấm “Insert 18” để xem các phần tử bị dịch chuyển

Sorted array không phải hoàn toàn vô dụng, nó cho phép tìm kiếm cực nhanh nhờ Binary Search (O(log n)). Vấn đề duy nhất là ghi quá chậm (O(n)) vì phải dịch chuyển dữ liệu. Vậy có cách nào vừa tìm nhanh, vừa không phải dịch chuyển hàng loạt khi ghi?

Database giải quyết bằng một ý tưởng then chốt: thay vì ép tất cả vào một mảng khổng lồ, hãy chia nhỏ sorted array thành nhiều page (gọi là leaf node), nối chúng bằng Doubly Linked List để duyệt tuần tự, rồi xây một cây phân cấp (tree) phía trên để tìm nhanh. Kết quả chính là B-Tree, cấu trúc dữ liệu mà hầu hết database hiện đại đều sử dụng.

  • Leaf Node (tầng lá) - Mỗi page chứa hàng trăm key đã sắp xếp, tận dụng tối đa mỗi lần đọc disk.
  • Doubly Linked List (cầu nối) - Nối các leaf node lại với nhau, cho phép duyệt tuần tự mà không cần quay lại root.
  • Tree (tầng trên) - Cây phân cấp đóng vai trò bản đồ, cho phép nhảy thẳng đến đúng leaf node cần thiết, O(log n).

Ba thành phần này kết hợp lại tạo nên B-Tree. Các phần tiếp theo sẽ lần lượt “mổ xẻ” từng thành phần để hiểu rõ cách chúng hoạt động, trước khi ghép tất cả lại thành một cấu trúc hoàn chỉnh.

Phần 6: Giải phẫu một Leaf Node

Ở phần trước, chúng ta đã biết database chia dữ liệu index thành các page nhỏ gọi là leaf node, nơi dữ liệu index thực sự được lưu trữ. Bây giờ hãy “mở nắp” một leaf node ra xem bên trong có gì.

0:00/0:00

Video vừa rồi cho bạn thấy cái gì nằm trong một leaf node. Nhưng có một câu hỏi mà chỉ nhìn thì không trả lời được: nhét được bao nhiêu entry vào một page thì đủ? Con số đó có tên là fanout, và nó quyết định cây của bạn cao hay lùn.

Hãy tự tay đổi thông số bên dưới. Hai chiếc hộp giữ nguyên khung để so sánh công bằng, chỉ khác nhau ở mức độ lấp đầy.

Bàn mổ một Leaf Node

Đổi page size và kiểu key, xem page tự đóng gói lại và cây cao lên hay lùn xuống.

Page size
Kiểu key
Số dòng của bảng
Leaf Node của B+Tree737 entries nhét vừa 1 page
page header + fillfactor
Nếu dùng BST1 entry duy nhất, phí 99.88%
← đúng 20 byte thật sự dùng
Mỗi entry nặng
20 B
4B key + 16B overhead
Fanout (entry / page)
0
cũng là số nhánh mỗi node trong
Chiều cao cây
0 tầng
100 triệu dòng · mỗi tầng = 1 lần đọc disk
BST sẽ cần
27 lần đọc
B+Tree chỉ cần 3

Giữ nguyên cấu hình mặc định (page 16 KB, 100 triệu dòng) rồi đổi key từ INT sang UUID: key to gấp 4 lần, fanout tụt xuống, và cây phải cao thêm một tầng. Cao thêm một tầng nghĩa là mọi câu query dùng index này đều tốn thêm một lần đọc disk. Đó là lý do khoá chính nên nhỏ gọn.

Mô hình quy ước mỗi entry = key + 16B con trỏ, ở leaf là con trỏ tới row còn ở node trong là con trỏ tới page con, nên fanout đồng nhất mọi tầng. Con số tuyệt đối sẽ lệch so với engine thật; điều cần đúng là quan hệ giữa chúng.

Chuỗi nhân quả cần nhớ:

Key to hơn → mỗi page nhét được ít entry hơn → fanout giảm → cần nhiều tầng hơn để chứa cùng lượng dữ liệu → mỗi lần tra cứu tốn thêm disk I/O.

Cả chuỗi này bắt nguồn từ đúng một điều đã học ở Phần 1: database đọc theo đơn vị page. Kích thước key nghe như chuyện vặt, nhưng nó lan tới tận số lần chạm đĩa của mọi câu query.

Quick Quiz

1.Trong InnoDB (MySQL), mỗi Leaf Node (page) có kích thước bao nhiêu?
1/1
1/1

Phần 7: Doubly Linked List: Cầu nối giữa các Leaf Node

Thứ tự trên 2 cấp độ

Index duy trì thứ tự trên 2 cấp độ đồng thời:

  • Cấp 1: Bên trong mỗi leaf node - Các entries sắp xếp tăng dần theo key value. Ví dụ: [11, 13, 18] luôn có thứ tự.
  • Cấp 2: Giữa các leaf node - Doubly linked list nối các nodes lại. Node chứa [11,13,18] → Node chứa [21,27,27] → Node chứa [34,35,39].

Tiếp tục câu chuyện về những “chiếc hộp” Leaf Node. Nếu Leaf Node là những hòn đảo chứa kho báu, thì Doubly Linked List (Danh sách liên kết đôi) chính là hệ thống cầu nối giúp chúng ta di chuyển giữa các đảo mà không cần quay lại đất liền.

Tại sao database lại chọn kiến trúc này? Dưới đây là 3 lý do “sống còn”:

1. Duyệt tuần tự: Range Query cực nhanh

Đây là lý do quan trọng nhất. Khi bạn viết WHERE price BETWEEN 100 AND 500, database dùng tree để nhảy thẳng tới leaf node chứa giá trị 100. Sau đó, thay vì phải leo ngược lên root để tìm giá trị tiếp theo, nó chỉ cần đi theo con trỏ “next” sang leaf node kế tiếp, cứ thế chạy dọc cho đến khi vượt quá 500.

Không có linked list, mỗi lần muốn sang leaf node kế tiếp, database phải leo ngược lên parent rồi đi xuống lại, có thể tốn nhiều lần disk I/O. Linked list biến thao tác này thành một bước duy nhất.

  • Không có linked list - Mỗi lần sang leaf node kế tiếp phải leo lên parent → đi xuống lại. Trong cây 4 tầng, mỗi bước nhảy có thể tốn 6+ lần đọc disk.
  • Có linked list - Theo con trỏ next/previous, chỉ 1 lần đọc disk. Hiệu quả gấp bội cho range query và sequential scan.

2. Duyệt ngược: một index phục vụ cả hai chiều

0:00/0:00

3. Chèn và xoá cực rẻ: chỉ cần nối lại con trỏ

Nhớ lại nỗi đau của sorted array ở Phần 5: chèn một giá trị vào giữa là phải dịch chuyển hàng loạt phần tử phía sau, O(n). Linked list không có nỗi đau đó. Muốn chèn một leaf node mới vào giữa hai node cũ, database chỉ việc cắt liên kết cũ rồi nối lại 2 con trỏ, không phải đụng vào bất kỳ node nào khác.

Chi phí là O(1), không đổi dù index đang có 10 leaf node hay 10 triệu. Bấm thử animation dưới đây để xem đúng 2 con trỏ đó được cắt và nối lại như thế nào.

Doubly Linked List, Insert O(1)

Node 111, 13, 18Node 221, 27, 27Node 334, 35, 39New Node19, 20, 21← Doubly Linked List: mỗi node có con trỏ prev và next →

Bấm “Insert Node” để xem cách chèn node mới vào Linked List

Quick Quiz

1.Tại sao bạn KHÔNG cần tạo 2 index riêng biệt cho ORDER BY ASC và ORDER BY DESC?
1/2
1/2

Phần 8: Vật lý và Logic: hai thế giới không trùng nhau

Đến đây bạn đã có một hình dung rất gọn gàng: các leaf node xếp thành hàng, nối nhau bằng linked list, tăng dần từ trái sang phải. Hình dung đó đúng về mặt logic.

Nhưng trên ổ cứng thật thì không có hàng lối nào cả. Đây là phần giải thích vì sao sự lệch pha giữa hai thế giới đó lại quyết định tốc độ của rất nhiều câu query.

0:00/0:00

Hãy tự tay tạo ra sự hỗn loạn đó. Bấm INSERT 1 dòng vài lần và để ý: mỗi dòng mới rơi vào một block trống bất kỳ, còn cột index bên phải thì lúc nào cũng xếp đúng A → Z.

Sau đó cho đầu đọc chạy theo hai thứ tự khác nhau trên cùng ngần ấy dòng và so quãng đường nó phải di chuyển.

Hỗn loạn vật lý, trật tự logic

Chèn dòng vào bảng heap, rồi cho đầu đọc chạy theo hai thứ tự khác nhau trên cùng ngần ấy dòng.

Bảng gốc trên disk (heap)xếp theo thứ tự CHÈN
Index (leaf nodes)luôn xếp theo A → Z

Chèn vài dòng để thấy index tự sắp xếp

Đọc theo thứ tự Index
-
random I/O, đầu đọc nhảy loạn
Đọc theo thứ tự vật lý
-
sequential I/O, chạy gần như thẳng
Chênh lệch
-
cùng ngần ấy dòng, khác mỗi thứ tự đọc

Index sắp theo giá trị key, còn bảng gốc ném dòng mới vào bất kỳ block nào còn trống, tức theo thứ tự CHÈN. chien và chuong đứng sát nhau trong index chỉ vì tên gần nhau về alphabet, nhưng hãy nhìn số thứ tự chèn trên lưới disk: một cái vào thứ 2, cái kia mãi thứ 8. Hai dòng sinh ra cách nhau rất xa về thời gian nên nằm ở hai nơi khác hẳn trên ổ cứng. Đó là lý do đọc nhiều dòng qua index lại sinh ra random I/O đắt đỏ, và cũng là lý do tồn tại của clustered index mà bạn sẽ gặp ở module sau.

Vì sao điều này quan trọng về sau:

Index cho bạn thứ tự logic miễn phí. Nhưng lấy dữ liệu thật thì vẫn phải quay về bảng gốc, nơi các dòng nằm rải rác theo thứ tự chèn. Càng lấy nhiều dòng qua index, càng nhiều cú nhảy ngẫu nhiên trên disk.

Đó là lý do một câu query trả về quá nhiều dòng thì database thà quét thẳng cả bảng còn nhanh hơn là đi qua index. Nghe vô lý, nhưng bạn vừa nhìn thấy con số của nó. Bài 3 chiến thuật Scan sẽ mổ xẻ đúng ngưỡng đánh đổi này.

Quick Quiz

1.Vì sao hai entry nằm sát nhau trong index lại có thể trỏ tới hai vị trí cách rất xa nhau trên disk?
1/2
1/2

Phần 9: B-Tree: Tấm bản đồ kho báu

0:00/0:00

Sức mạnh của con số

Trong InnoDB (MySQL), mỗi page là 16KB. Mỗi node B-Tree chiếm đúng 16KB, vừa khít một page. Một node internal có thể chứa khoảng 500 đến 1000 key tuỳ kích thước key.

Giả sử mỗi node chứa được khoảng 500 key. Một cây B-Tree 3 tầng có thể chứa:

500 × 500 × 500 = 125 TRIỆU bản ghi: Bạn đọc lại đi: BA lần đọc disk, chỉ BA, để tìm ra một bản ghi trong 125 triệu dòng. So với BST cần 27 lần đọc (log₂ của 125 triệu).

Và thực tế còn ngon hơn thế: node gốc (root) gần như luôn nằm sẵn trong RAM vì nó được truy cập liên tục. Nên thực tế thường chỉ cần 2 lần đọc disk, đôi khi chỉ 1 lần.

Quy tắc xây dựng B-Tree

Mỗi entry trong branch node chứa giá trị lớn nhất (max key) của leaf node tương ứng bên dưới, kèm con trỏ đến leaf node đó. Ví dụ: leaf node chứa [46, 53, 53] → branch entry = 53.

Tầng branch được xây từ dưới lên: gom tất cả max keys của leaf nodes → tạo branch layer. Nếu branch layer vẫn quá lớn → tạo thêm tầng branch nữa, cho đến khi toàn bộ fit vào 1 node duy nhất = Root Node.

Xây dựng B-Tree từ dưới lên

Leaf111318Leaf2127Leaf303539Leaf46Leaf485153Leaf555757Leaf5862Leaf758083Leaf8590Leaf9295Leaf9798Leaf105Leaf110120← Doubly Linked List giữa các Leaf Nodes →Branch182739Branch46535783Branch909598Branch105120Root39839818273946535783909598105120398398

Bấm “Xây B-Tree” để xem cách cây được xây từ dưới lên

B-Tree Traversal, Tìm key = 57

Chọn key để tìm
Root39839839, 83, 98Branch18, 27, 39Branch4653578346, 53, 57, 83Branch90, 95, 98Branch105, 120Leaf11, 13, 18Leaf21, 27Leaf30, 35, 39Leaf4646Leaf48515348, 51, 53Leaf55575755, 57, 57Leaf586258, 62Leaf75808375, 80, 83Leaf85, 90Leaf92, 95Leaf97, 98Leaf105Leaf110, 12057Chỉ các node trên đường đi được highlight, các node khác mờ đi

Bấm “Search 57” để xem đường đi từ Root xuống Leaf

B-Tree Traversal: Ví dụ tìm key = 57

Bước 1: Root Node: [39, 83, 98]

Duyệt: 39 < 57 → tiếp. 83 ≥ 57 → đi theo pointer của 83 xuống branch node giữa.

Bước 2: Branch Node: [46, 53, 57, 83]

Duyệt: 46 < 57 → tiếp. 53 < 57 → tiếp. 57 ≥ 57 → đi theo pointer của 57 xuống leaf node.

Bước 3: Leaf Node: [55, 57, 57]

Tìm thấy 2 entries có key=57! Tổng cộng: chỉ đọc 3 blocks (1 root + 1 branch + 1 leaf).

Trên SSD thì sao? Khoảng 0.3ms. Đó là sức mạnh của B-Tree.

Quick Quiz

1.Một B-Tree 3 tầng, mỗi node chứa 500 key, có thể chứa tối đa bao nhiêu bản ghi?
1/2
1/2

Ghép tất cả lại: B-Tree Insert

Qua các phần vừa rồi, bạn đã hiểu từng thành phần: Leaf Node chứa data, Doubly Linked List nối chúng lại, và Tree dẫn đường từ trên xuống. Bây giờ hãy xem chúng phối hợp với nhau như thế nào khi INSERT một key mới vào B-Tree:

Dự đoán trước khi xem

1.Ở Phần 5, chèn 1 key vào sorted array phải dịch hàng loạt phần tử phía sau. Bây giờ chèn key 18 vào B-Tree, với leaf node đích vẫn còn chỗ trống. Database phải GHI LẠI bao nhiêu block trên disk?
1/1
1/1

B-Tree Insert, Duyệt Root → Branch → Leaf

Root20, 40Branch10Branch25, 30Branch50Leaf3, 5, 8Leaf10, 12, 15Leaf20, 22Leaf25, 27, 28Leaf30, 33, 37Leaf40, 42, 45Leaf50, 55, 6018← Doubly Linked List giữa các Leaf Nodes →

Bấm “Insert 18” để xem đường đi từ Root xuống Leaf

Phần 10: B+Tree: Cái tên thật của thứ bạn đã học

0:00/0:00

Tóm lại, B+Tree cải tiến B-Tree thuần ở 2 điểm:

B-Tree thuầnB+Tree (cái bạn đã học)
Linked List giữa leaf?Không có, phải quay lại parentCó, Doubly Linked List nối các leaf
Data ở đâu?Mọi node (root, branch, leaf)Chỉ leaf node
Internal node chứa gì?Key + data → ít key/nodeChỉ key + pointer → nhiều key/node
Chiều cao cây?Cao hơn (ít key → nhiều tầng)Lùn hơn (nhiều key → ít tầng)
Range queryChậm, leo lên leo xuống treeNhanh, lướt theo linked list
Database nào dùng?Hầu như không còn dùngMySQL, PostgreSQL, SQLite, Oracle...
Tại sao mọi người vẫn gọi là “B-Tree”?: Trong tài liệu PostgreSQL, MySQL docs, và hầu hết sách thực hành, thuật ngữ “B-Tree index” thực chất đang nói về B+Tree. Đây là quy ước ngành, giống như mọi người gọi “xe hơi” dù engine bên trong đã thay đổi hoàn toàn so với thế kỷ trước.

Range Query: nơi B+Tree tỏa sáng

Khi bạn viết:

Database tìm leaf node chứa giá trị 100 (chỉ cần 2-3 lần đọc disk nhờ cây lùn), rồi lướt theo linked list qua các leaf liên tiếp cho đến khi vượt quá 500. Toàn bộ quá trình là sequential I/O (tuần tự), nhanh gấp bội so với phải leo lên leo xuống cây như B-Tree thuần.

Dự đoán trước khi xem

1.Animation bên dưới chạy cùng một range query lấy các key từ 100 đến 500, trải trên 5 leaf node liên tiếp. B+Tree lướt theo linked list và tốn 7 lần đọc. Theo bạn B-Tree thuần, loại KHÔNG có linked list giữa các leaf, tốn khoảng bao nhiêu?
1/1
1/1

Range Query, B+Tree vs B-Tree

B+Tree250,500100,250400,50070050,80100,150200,250300,350400,450500,550600,700800Disk reads: 0B-Tree250,500100,250400,50070050,80100,150200,250300,350400,450500,550600,700800Disk reads: 0

Bấm để so sánh range query trên B+Tree vs B-Tree

Quick Quiz

1.Cấu trúc index bạn học ở các phần trước (leaf nodes + linked list + tree) thực ra là gì?
1/2
1/2

Phần 11: Thực hành: sờ tận tay cái Index

Suốt 10 phần vừa rồi, Index là một thứ trừu tượng: cái cây trong hình vẽ, những chiếc hộp trong animation. Bây giờ bạn sẽ tạo ra nó trên một PostgreSQL thật và nhìn thấy nó tồn tại: nó là một object riêng, nó chiếm bao nhiêu disk, và nó nặng lên thế nào khi key to ra.

Ba điều bạn sẽ tự kiểm chứng, đều là những điều bài học đã hứa: index nằm tách biệt khỏi bảng gốc (Phần 3), key càng to thì index càng nặng (Phần 6), và một index phục vụ được cả hai chiều sắp xếp (Phần 7).

PostgreSQL 17 · Sandbox
Premium

Mở khoá SQL Sandbox: thực hành trên database thật

SQL Playground chỉ mở cho học viên đã mua khoá “SQL Nâng Cao”. Khi mua, bạn được chạy trực tiếp query trên PostgreSQL với dataset thật của bài học.

  • Chạy SQL trực tiếp trên PostgreSQL 17 thật, không phải kết quả mô phỏng
  • Mỗi học viên có schema riêng, tự do CREATE / INSERT / EXPLAIN
  • Reset dữ liệu về trạng thái gốc bất cứ lúc nào để thử lại
399.000₫
Bạn vừa tự tay chứng minh cái giá:

Ở Bước 4, ba index trên cùng một bảng, cùng số dòng lại có dung lượng khác hẳn nhau, chỉ vì kích thước key khác nhau. Và mỗi index trong đó đều sẽ phải được cập nhật mỗi lần bạn INSERT, UPDATE hay DELETE.

Đó chính là cuộc đánh đổi mà bài tiếp theo sẽ đo đếm sòng phẳng: Cái giá của Index. Còn cách bắt database khai ra nó đã chọn chiến thuật nào để chạy query, bằng lệnh EXPLAIN, sẽ là nội dung bài Equality & EXPLAIN Plan.

Kiểm tra kiến thức

10 câu dưới đây không hỏi bạn nhớ được con số nào, mà hỏi bạn có dùng được những gì vừa học để suy luận ra câu trả lời hay không. Làm hết một lượt rồi xem phần giải thích của những câu sai.

Kiểm tra kiến thức toàn bài

1.Bảng users có 50 triệu dòng, đã đánh index trên email. Query WHERE email = ? tốn khoảng 3 lần đọc disk. Nếu dữ liệu tăng gấp 10 lần lên 500 triệu dòng, số lần đọc disk sẽ là bao nhiêu?
1/10
1/10

Tổng kết

Hành trình của bài này:Từ câu hỏi “tại sao query chậm?” → hiểu disk I/O là nút thắt → Full Table Scan đọc hết mọi page → BST lãng phí 99.9% mỗi page → xây dựng cấu trúc index từng bước (Leaf Node → Linked List → Tree) → và cuối cùng nhận ra: cái chúng ta xây chính là B+Tree, cấu trúc mà mọi database hiện đại đều dùng.

Disk rất chậm: chậm bao nhiêu?

Nhấp vào thẻ để lật

HDD chậm hơn RAM 100.000 lần. Bài toán cốt lõi: giảm số lần đọc disk.

Nhấp vào thẻ để lật lại
1/7
Ở bài tiếp theo, chúng ta sẽ đi vào thực tế: 3 bước của Index Lookup, khi nào index phản tác dụng, và trade-offs khi đánh index.
Bài tiếp theo
range_query.sql
SQL
1
SELECT * FROM orders WHERE price BETWEEN 100 AND 500;