Skip to content
本站總訪問量
本站訪客數 人次

進階索引實戰:B-Tree、複合索引與 Hash Map 全解析

本文將「索引」觀念與前端離線快取 (Service Worker / PWA) 類比,幫助剛接觸後端資料庫的新手也能快速理解「為何加索引像是替資料上快取」。全篇以 MySQL 為例,但概念同樣適用於 PostgreSQL、SQL Server 等主流 RDBMS。

📋 文章目錄

  1. 索引 ≈ PWA 快取:觀念對照圖
  2. B-Tree/B+Tree:關聯式資料庫的預設索引
  3. Hash Index:O(1) 等值查詢神器
  4. 複合索引(Composite Index)
  5. 快取策略 vs. 索引策略
  6. 實戰案例:從 3 秒 → 50 毫秒
  7. 索引維護與監控
  8. 常見地雷與排雷技巧

0. 索引 ≈ PWA 快取:觀念對照圖

Web PWADatabase共通核心
Service Worker Cache APIB-Tree / Hash Index以空間換時間,加速存取
Cache Key索引 Key唯一定位資料位置
Cache First / Network FirstIndex Lookup / Table Scan先查快取 (索引),再回源 (資料表)
緩存更新 (Revalidate)索引維護 (UPDATE / REBUILD)保持資料與索引一致

結論:快取如何讓 PWA 離線秒開,索引就如何讓資料庫查詢「不掃全表」。


1. B-Tree/B+Tree:關聯式資料庫的預設索引

1-1 為何不是二元樹?

磁碟 I/O 一次可讀取整個 block (4K/8K),若用二元樹,每層只存 2 個指標,會造成 I/O 次數過多。B-Tree 允許 m 階,一頁存放大量 key,從而大幅降低樹高[25][29]。

1-2 結構分層

Root (1 page)
 ├─ Internal Node (多頁)
 └─ Leaf Node (大量頁) ← 實際儲存資料位置 / PK 值
  • 葉層 按 key 有序鏈結,可做範圍掃描 (between、> <)。
  • 中間層 只存 key+child pointer,加快導向。
  • 根層 永遠在記憶體,搜尋複雜度 O(logₘ n)

類比 Service Worker:Root 像 JS Runtime 常駐記憶體的快取索引表,Leaf 像實際 Cache Storage blob。

1-3 建立範例

sql
-- email + created_at 結合範圍 + 等值
CREATE INDEX idx_user_email_date ON users(email, created_at);

-- 解讀
-- 1. email 完全比對時 → 直接走索引
-- 2. email 比對後再依 created_at 做區段掃描 → 善用葉層有序

1-4 注意事項

  1. 最左前綴:查詢條件需含最左欄位才能命中索引。
  2. 回表 (Key Lookup):非覆蓋欄位須再到資料頁讀取,等同 PWA Cache Miss。

2. Hash Index:O(1) 等值查詢神器

2-1 運作流程

  1. 對 key 執行雜湊函式 hash(key) → 得 bucket 位址。
  2. 直接定位資料列 (或 PK)。

複雜度 O(1),缺點是不支援範圍、排序,也無法利用最左前綴原則[30]。

sql
-- InnoDB MEMORY Engine 才支援顯式 HASH
CREATE TABLE hotspot (
  id INT PRIMARY KEY,
  code CHAR(8),
  VALUE INT,
  INDEX USING HASH (code)
) ENGINE=MEMORY;

2-2 Hash Map 類比

js
// JS 雜湊表:Key => Value
const map = new Map();
map.set('code_abc', 123);
console.log(map.get('code_abc')); //  O(1)

資料庫 Hash Index 與 JavaScript Map 原理一致:雜湊衝突 (collision) 需鏈結或開放定址處理


3. 複合索引(Composite / Multiple Column Index)

3-1 建立原則

  1. 選擇性最高 (cardinality) 放最左
  2. 將常用且固定的等值欄位放前,範圍條件塞尾端
sql
-- 依以下查詢頻率排序
-- a. WHERE country='TW' AND status='paid' AND created_at>'2025-01-01'
-- b. WHERE country='TW' AND status='pending'
CREATE INDEX idx_orders_country_status_date
ON orders(country, status, created_at);

3-2 覆蓋索引 (Covering Index)

SELECT 欄位完全被索引覆蓋 → 不需回表

sql
-- 索引欄位已包含查詢所需欄位 total
SELECT country, status, total
FROM   orders FORCE INDEX (idx_orders_country_status_date)
WHERE  country='TW' AND status='paid'; -- 直接返回索引頁

結果:I/O 降至單純索引掃描,等同 PWA Cache First 策略。


4. 快取策略 vs. 索引策略:最佳化思路對照

PWA 策略資料庫對應適用場景
Cache First覆蓋索引 (只走索引頁)熱門讀多寫少清單
Network First非覆蓋索引 + 回表資料即時性高,需要最新 row
Stale-While-Revalidate舊索引保留 + 背景 REBUILD夜間批次重建大索引

TIP:夜深人靜做 OPTIMIZE TABLE = 背景 Revalidate Cache。


5. 實戰:從 3 秒 → 50 毫秒

5-1 問題描述

sql
SELECT order_id, total
FROM   orders
WHERE  user_id = 42
AND    status = 'paid'
AND    order_date BETWEEN '2025-01-01' AND '2025-07-01';
  • 100 萬筆訂單,EXPLAIN 顯示 type: ALL,耗時 3 秒。

5-2 解決方案

sql
CREATE INDEX idx_orders_user_status_date
ON orders(user_id, status, order_date);

5-3 成果驗證

sql
EXPLAIN SELECT ... ;
-- type: RANGE / rows: 120 →  I/O 大減
-- 實測耗時 ~50 ms

6. 索引維護與監控

  1. 重組 / 重建ALTER INDEX ... REBUILD; (SQL Server) 或 OPTIMIZE TABLE (MySQL)
  2. 碎片率:>30% 建議 REBUILD
  3. 冗餘索引偵測:慢查詢日誌 + SHOW INDEX + 卡片法清理
  4. 統計資訊更新ANALYZE TABLE 保證最佳化器選對索引

7. 常見地雷與排雷技巧

地雷解法
在索引欄位使用函數 YEAR(created_at)建立 函數索引 (Expression Index) 或改寫範圍查詢
LIKE '%keyword%' 前置百分比使用全文索引 (FULLTEXT, GIN)
多欄都建單欄索引 → 過度索引合併為複合索引,或只保留高選擇性
低選擇度欄位建索引 (gender)搭配高選擇度欄或省略索引

8. 小結

  1. B-Tree 適用大部分範圍/排序查詢;
  2. Hash Index 適用極端熱點等值查詢;
  3. 複合索引 = 把常用條件壓縮進單一樹;
  4. 索引就像快取:讀快、寫慢、需清理;
  5. 定期維護、監控慢查詢,才能持續保持「秒開體驗」。

下一步:動手用 EXPLAIN 為你的專案打造「量身訂做」的索引組合,像調 Service Worker Cache Policy 一樣,讓資料庫也能一樣快!

常見問題 FAQ

Q1: B-Tree 和 Hash 索引該如何選擇?

A: 選擇依據:

  • B-Tree:適用範圍查詢、排序、模糊搜尋(LIKE 'prefix%'
  • Hash:純等值查詢,如用戶ID查詢、狀態碼比對
  • 混合策略:熱點數據用Hash,一般查詢用B-Tree

Q2: 複合索引的欄位順序如何決定?

A: 遵循以下原則:

  1. 等值條件 > 範圍條件
  2. 高選擇性 > 低選擇性(cardinality高的在前)
  3. 查詢頻率高 > 查詢頻率低
sql
-- 優先順序:country(等值) > status(等值) > created_at(範圍)
CREATE INDEX idx_orders_country_status_date 
ON orders(country, status, created_at);

Q3: 如何判斷索引是否需要重建?

A: 監控指標:

  • 碎片率 > 30%:需要重建
  • 索引深度過深:樹高 > 4層
  • 查詢效能下降:相同查詢變慢
sql
-- MySQL 檢查索引碎片
SHOW TABLE STATUS LIKE 'table_name';
-- 重建索引
OPTIMIZE TABLE table_name;

Q4: 覆蓋索引和非覆蓋索引的效能差異?

A: 效能對比:

  • 覆蓋索引:只需索引掃描,I/O次數少
  • 非覆蓋索引:需要回表查詢,額外I/O成本

實測數據顯示覆蓋索引可提升 2-5倍 查詢效能。

Q5: 什麼情況下不建議使用索引?

A: 避免索引的情況:

  • 小表(< 1000筆資料)
  • 寫入頻繁的表(寫入:讀取 > 3:1)
  • 低選擇性欄位(如 gender, status 只有2-3個值)

進階索引效能測試

索引類型查詢類型100萬筆數據耗時適用場景
B-Tree等值查詢2-5ms通用查詢
B-Tree範圍查詢10-50ms日期、數值範圍
Hash等值查詢1-2ms熱點數據查詢
複合索引多條件查詢1-3ms複雜業務查詢
覆蓋索引特定欄位查詢0.5-1ms報表、統計查詢

測試環境:MySQL 8.0,SSD硬碟,16GB記憶體

延伸閱讀


📝 作者資訊: 本文由許恩綸撰寫,專注於資料庫效能優化與後端架構設計。

🔄 最後更新: 2025年7月5日

⚡ 效能提升: 透過本文技巧,可將查詢效能提升 60-1500倍

Contributors

The avatar of contributor named as lucashsu95 lucashsu95

Changelog