Quay lại
Công Nghệ

SQL Index Under the Hood (Part 2)

5 phút đọc8 thg 7, 2026
H

hoanggg2110

Tác giả

SQL Index Under the Hood (Part 2)

SQL Index Under the Hood (Part 2): Làm sao Database tìm được 1 dòng giữa 10 triệu records?

Ở Part 1, ta đã biết: không có Index → Database chỉ có một cách — đọc lần lượt từng Page. Ổn với vài nghìn dòng. Nhưng 10 triệu dòng thì sao?

Ví dụ: tìm người trong danh bạ

Database làm điều tương tự — nhưng bằng một cấu trúc tên là B-Tree. Nghe đáng sợ, nhưng bạn dùng nó mỗi ngày.

Mental model: sơ đồ trung tâm thương mại

Vào Aeon Mall tìm Uniqlo, bạn không chạy khắp nơi — bạn nhìn Directory: Tầng 1 → Tầng 2 → Fashion → Uniqlo.

B-Tree hoạt động y hệt: nó không đưa bạn dữ liệu ngay, nó chỉ chỉ đúng hướng để đi tiếp.

Vấn đề nếu không có "đường tắt"

Nếu dữ liệu chỉ là một chuỗi nối tiếp (1 → 2 → 3 → ... → 10.000.000), tìm số 8.200.315 nghĩa là đi từng bước — không khác gì Full Scan.

B-Tree ra đời: xây thêm các tầng

Thay vì một đường thẳng, B-Tree chia dữ liệu thành các nhánh:

                 Root
              3,000,000
          /              \
     <3,000,000      >3,000,000

Tìm 8.200.315? Database biết ngay: đi phải. Cây càng sâu, khoảng tìm kiếm càng thu hẹp, đến khi chỉ còn đúng 1 Page.

🔍 Vậy chính xác thì B-Tree được tổ chức như thế nào?

SQL Index Under the Hood (Part 2)

Vài điểm quan trọng mà ví dụ ở trên chưa nói hết:

1. Mỗi node chứa nhiều "key", không chỉ một Trong thực tế, một node của B-Tree không chỉ giữ đúng 1 giá trị như 3,000,000 — nó có thể chứa hàng trăm giá trị được sắp xếp sẵn, mỗi giá trị đi kèm một con trỏ trỏ đến nhánh con phù hợp. Đây là lý do B-Tree có fan-out (số nhánh con) rất cao, thường vài trăm nhánh mỗi tầng.

2. Vì sao fan-out cao lại quan trọng? Fan-out càng cao, cây càng "lùn và rộng" thay vì "cao và mảnh". Với fan-out ~300, chỉ cần 4 tầng là đủ để đánh chỉ mục hơn 8 tỷ dòng dữ liệu. Đó là lý do dù bảng có 10 triệu hay 100 triệu dòng, số lần đọc để tìm một giá trị vẫn chỉ dao động quanh 3-5 lần — gần như không đổi.

3. "B" trong B-Tree nghĩa là Balanced (cân bằng) Dù bạn insert hay delete dữ liệu liên tục, B-Tree luôn tự tổ chức lại để mọi nhánh lá (leaf) nằm ở cùng một độ sâu. Không có chuyện một nhánh sâu 3 tầng, nhánh khác sâu 10 tầng. Điều này đảm bảo thời gian tìm kiếm luôn ổn định, bất kể bạn tìm giá trị nào.

4. Leaf node không chỉ để dừng lại — chúng được nối với nhau Các leaf node trong B-Tree (thực ra PostgreSQL dùng biến thể B+Tree) được liên kết thành một danh sách liên kết ngang. Nhờ vậy, khi bạn query dạng range như WHERE id BETWEEN 100 AND 200, Database chỉ cần tìm đến leaf đầu tiên rồi đi ngang sang các leaf kế tiếp, thay vì phải quay lại Root mỗi lần.

5. Leaf node lưu gì? Đây là điểm hay bị hiểu lầm: leaf node không lưu toàn bộ dòng dữ liệu, mà chỉ lưu giá trị được index (ví dụ email) kèm theo một con trỏ (CTID trong PostgreSQL) trỏ đến vị trí thật của dòng đó trong bảng. Đây chính là lý do bước tiếp theo — nhảy từ Index sang bảng thật — luôn cần thêm một bước riêng (mình sẽ nói kỹ ở phần dưới).

Tóm gọn: B-Tree không phải là "cây nhị phân đơn giản" như trong sách giáo trình — nó được thiết kế đặc biệt để tối ưu số lần đọc Page từ đĩa, với node rộng, cây thấp, và luôn cân bằng.

Điều kỳ diệu: với 10 triệu dòng, Full Scan có thể đọc 10 triệu dòng — B-Tree chỉ cần 4–5 lần đọc (Root → Internal → Internal → Leaf). Index nhanh không phải vì CPU khỏe hơn, mà vì đọc ít hơn rất nhiều.

Hiểu lầm thường gặp

Nhiều người nghĩ B-Tree tìm xong (Root → Leaf) là kết thúc. Sai. Đó mới là Index Scan. Sau đó Database còn phải nhảy sang bảng thật để lấy dữ liệu và trả về — đây chính là nguồn gốc của không ít query chậm (hẹn ở Part 3).

Hands-on Lab

Không có index

SQL Index Under the Hood (Part 2)

Tạo index

CREATE INDEX idx_customer_email ON customers(email);

EXPLAIN ANALYZE
SELECT * FROM customers WHERE email='customer_999999@gmail.com';

Lần này bạn sẽ thấy Index Scan thay vì Seq Scan.

SQL Index Under the Hood (Part 2)

Benchmark tham khảo

(Số liệu minh họa xu hướng — nên tự chạy EXPLAIN (ANALYZE, BUFFERS) trên máy bạn để lấy số thật cho bài viết)

SQL Index Under the Hood (Part 2)

Điểm mấu chốt: Seq Scan tăng gần tuyến tính theo dữ liệu; Index Scan gần như không đổi.

Production Tip

Trước khi tạo Index, luôn tự hỏi:

  1. Query nào đang chậm?

  2. Nó lọc theo cột nào?

  3. Mỗi lần trả về bao nhiêu dòng?

Tổng kết

Part 3 – The Optimizer: Vì sao PostgreSQL đôi khi cố tình không dùng Index dù nó nhanh hơn? Query Planner nghĩ gì khi chọn Seq Scan thay vì Index Scan?

Thích bài viết này?

Nội dung trên Vết Mực luôn được chia sẻ miễn phí. Nếu bài viết mang lại giá trị cho bạn, hãy cân nhắc ủng hộ để chúng mình có thể duy trì máy chủ, phát triển thêm tính năng mới và tiếp tục xây dựng một không gian dành cho những người yêu viết lách. ✨

Các cách ủng hộ:

  • Viết và đăng bài trên Vết Mực
  • Chia sẻ bài viết với bạn bè
  • Góp ý để chúng mình cải thiện sản phẩm qua email: nsikhoa@gmail.com

Dù bạn chọn ủng hộ hay chỉ đơn giản là tiếp tục đọc và chia sẻ bài viết, đó đều là nguồn động lực rất lớn với chúng mình. ❤️

Bình luận

Đăng nhập để để lại bình luận.