iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Software Development

30 天從 Full-Stack Engineer 進化到 System Design:從 0 設計可支撐百萬使用者的系統系列 第 6

# Day 6|Database Index:為什麼加一個 Index,Query 可以快這麼多?

  • 分享至 

  • xImage
  •  

Day 5 我們討論 SQL vs NoSQL,也用 Shopping Cart 設計了 Customer、Product、Cart、Order 與 Transaction。

現在假設我們選擇 PostgreSQL,而且 users Table 從幾千筆成長到:

1,000 rows
    ↓
1,000,000 rows
    ↓
100,000,000 rows

一個普通 Query:

SELECT *
FROM users
WHERE email = 'alvin@example.com';

可能開始變慢。

Database 要解決的問題是:

我要怎麼在大量資料中快速找到 Alvin?

這就是今天的主題:

Database Index


沒有 Index 會發生什麼?

如果 email 沒有適合的 Index,Database 可能需要檢查大量 Rows:

Row 1 → not Alvin
Row 2 → not Alvin
Row 3 → not Alvin
...
Row 10,000,000

這類情況可以先理解成:

Full Table Scan / Sequential Scan

資料越多,需要讀取的資料可能越多。


用電話簿理解 Index

假設有一本 1,000 頁的電話簿,要找:

Lin, Alvin

如果完全沒有排序:

Page 1
Page 2
Page 3
...

可能需要一直翻。

如果有索引:

A → Page 1
B → Page 50
...
L → Page 500

就可以快速縮小範圍。

Database Index 也是類似概念:

建立額外的資料結構,幫助 Database 更快定位資料。

例如:

CREATE INDEX idx_users_email
ON users(email);

概念上:

alvin@example.com
        ↓
      Index
        ↓
找到 Row Location
        ↓
     User Row

Index 本身也是資料

Index 不是免費的魔法。

Database 必須額外維護 Index:

Table Data
+
Index Data

當資料發生:

INSERT
UPDATE
DELETE

相關 Index 也可能需要更新。

所以:

Index 可以提升某些 Read Query,但會增加 Storage 與 Write Cost。

這就是第一個重要 Trade-off:

Read Performance ↑
Write Cost ↑
Storage ↑

B-Tree / B+ Tree

Relational Database 的一般 Index 常使用 B-Tree family 的資料結構,實際實作依 Database 而不同。

Day 6 不需要先深入演算法細節。

先理解:

Tree-based Index 可以利用排序好的結構,快速縮小搜尋範圍。

它不只適合:

WHERE id = 123;

也很適合 Range Query:

WHERE age > 30;

或:

WHERE created_at
BETWEEN '2026-01-01' AND '2026-12-31';

因為 Index Key 有順序,可以找到起點後繼續讀取某個範圍。


有 Index 就一定快嗎?

不一定。

例如:

SELECT *
FROM users
WHERE email = 'alvin@example.com';

Email 幾乎每個 User 都不同,因此通常具有較高 Selectivity。

Index 往往很有價值。

但:

SELECT *
FROM users
WHERE is_active = true;

如果:

95% Users 都是 active

這個 Query 仍然需要大量 Rows。

Database Optimizer 可能判斷直接掃描 Table 更划算。

因此:

有 Index 不代表 Database 一定會使用它。

Optimizer 會考慮:

Statistics
Selectivity
Estimated Cost
Query Pattern

Primary Key 與 Index

假設:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255)
);

Database 通常會為 Primary Key 建立對應的 Unique Index。

因此:

SELECT *
FROM users
WHERE id = 123;

通常可以有效利用 Index。


Composite Index

這是非常值得準備的面試觀念。

假設常見 Query:

SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'PAID';

可以考慮:

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);

這叫:

Composite Index

也就是:

(user_id, status)

一起建立 Index。


Composite Index 的順序很重要

假設:

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);

通常比較適合:

WHERE user_id = 123;

以及:

WHERE user_id = 123
AND status = 'PAID';

但只有:

WHERE status = 'PAID';

不一定能有效使用這個 Composite Index。

可以用電話簿理解。

如果排序方式是:

Last Name
   ↓
First Name

找:

Lin, Alvin

很方便。

只知道:

Last Name = Lin

也方便。

但只知道:

First Name = Alvin

就沒那麼方便。

這就是 Composite Index 常見的:

Leftmost Prefix

概念。


面試小題目

假設:

CREATE INDEX idx_employee
ON employees(department, salary);

Query A

SELECT *
FROM employees
WHERE department = 'IT';

Query B

SELECT *
FROM employees
WHERE department = 'IT'
AND salary > 100000;

Query C

SELECT *
FROM employees
WHERE salary > 100000;

一般來說:

A → 可以利用 Index
B → 可以很好地利用 Index
C → 不一定能有效利用這個 Composite Index

因為 Index 是從:

department

開始組織。


為什麼不能每個 Column 都加 Index?

假設:

id
name
email
age
city
country
created_at
updated_at
status

全部建立 Index。

每次:

INSERT

Database 不只需要新增 Row,也可能需要更新多個 Index。

UPDATE / DELETE 同樣需要維護相關 Index。

所以:

More Indexes
    ↓
某些 Reads Faster
    ↓
Writes More Expensive
    ↓
More Storage

因此 Index 應該根據:

Real Query Pattern

設計,而不是「有 Column 就加 Index」。


怎麼知道 Query 慢在哪?

不要:

Query 很慢
   ↓
直接加 Index

應該先:

Measure First

例如 PostgreSQL:

EXPLAIN
SELECT *
FROM users
WHERE email = 'alvin@example.com';

或:

EXPLAIN ANALYZE
SELECT *
FROM users
WHERE email = 'alvin@example.com';

可以幫助觀察:

使用什麼 Scan?
有沒有使用 Index?
預估讀多少 Rows?
實際花多少時間?

可能看到:

Seq Scan

或:

Index Scan

Performance Optimization 的習慣應該是:

Measure
  ↓
Find Bottleneck
  ↓
Optimize
  ↓
Measure Again

台積 IT 面試延伸:Database 很慢,你會怎麼辦?

這是很適合延伸準備的題型。

不要只回答:

Add Index

可以分步驟分析。

Step 1:確認真的 Database 慢嗎?

整個 Request:

Client
  ↓
Network
  ↓
Backend
  ↓
Database

Latency 也可能來自:

Backend Logic
External API
Network
Serialization

所以先定位 Bottleneck。

Step 2:找到 Slow Query

檢查:

Execution Time
Rows Scanned
Query Plan

Step 3:檢查 Index

例如:

SELECT *
FROM orders
WHERE user_id = 123;

如果 user_id 是非常常見的 Filter,可以考慮:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

Step 4:檢查 Query

不要習慣永遠:

SELECT *

如果只需要:

id
status
total_amount

就:

SELECT id, status, total_amount
FROM orders
WHERE user_id = 123;

另外也要注意:

N+1 Query
Unnecessary JOIN
Large Result Set
Missing Pagination

Step 5:Index 還是不夠呢?

如果 Query 已經最佳化,但 Database 整體 Traffic 還是過高,才繼續考慮:

Cache
Read Replica
Partitioning
Sharding

這些會在後面的文章繼續展開。


台積面試情境:1 億筆 Orders

延續 Day 5 Shopping Cart。

需求:

查詢某個 Customer 最近 20 筆已付款的 Orders。

Query:

SELECT *
FROM orders
WHERE customer_id = 123
  AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;

假設:

orders = 100,000,000 rows

我們可以開始思考 Composite Index:

CREATE INDEX idx_orders_customer_status_created
ON orders(customer_id, status, created_at DESC);

為什麼?

因為實際 Query Pattern 是:

customer_id = ?
status = ?
ORDER BY created_at

所以不是看到三個 Columns 就一定建立三個互不相關的 Index。

而是:

根據 Query Pattern 設計 Index。


Index 也可能沒辦法直接幫你

例如:

SELECT *
FROM users
WHERE LOWER(email) = 'alvin@example.com';

如果只有普通的:

INDEX(email)

Database 不一定能直接有效使用它處理 Expression。

另一個例子:

WHERE name LIKE '%alvin%'

一般 B-Tree Index 通常也不適合用來快速定位這種前面帶 % 的搜尋。

所以:

Index 是否有效,取決於 Query 寫法、Index 類型以及 Database Optimizer。


System Design 的 Bottleneck 思維

目前 Architecture:

                     ┌── Server #1
                     │
User → Load Balancer ├── Server #2
                     │
                     └── Server #3
                              ↓
                          Database

前幾天:

Day 3 → Application Scaling
Day 4 → Load Balancing
Day 5 → Database Selection
Day 6 → Query Performance

System Design 很常是一個循環:

Find Bottleneck
      ↓
Understand Why
      ↓
Choose Solution
      ↓
Understand Trade-off
      ↓
Measure Again

台積 IT 面試準備 Checkpoint

今天可以練習:

1. Database Index 是什麼?

2. 為什麼 Index 可以讓 Query 變快?

3. 沒有 Index 時可能發生什麼?

4. Full Table Scan 是什麼?

5. B-Tree / B+ Tree 為什麼適合 Database Index?

6. Index 有什麼缺點?

7. 為什麼不能每個 Column 都建立 Index?

8. Primary Key 和 Index 有什麼關係?

9. Composite Index 是什麼?

10. 為什麼 Composite Index 的 Column Order 很重要?

11. Leftmost Prefix 是什麼?

12. Query 很慢時,你會怎麼 Debug?

13. EXPLAIN / EXPLAIN ANALYZE 是做什麼?

14. 加了 Index 還是不夠,下一步可以考慮什麼?

15. 1 億筆 Orders,要查某個 Customer 最近 20 筆
    PAID Orders,你會怎麼設計 Index?

如果能不用背答案,用自己的話回答這些問題,就不只是知道:

Index = Faster Query

而是開始理解:

Query Pattern
Data Structure
Performance
Trade-off

今天學到了什麼?

核心概念:

Without Index
     ↓
可能掃描大量 Rows

With Index
     ↓
利用額外 Data Structure
     ↓
更快定位資料

但是:

Index ≠ Free Performance

因為需要:

Extra Storage
Write Maintenance
Index Maintenance

所以 Index Design 應該根據:

Query Pattern
Selectivity
Read / Write Ratio
Data Size

決定。

最重要的一句話:

不要因為 Query 慢就盲目加 Index。先 Measure、找到 Bottleneck,再根據真正的 Query Pattern 設計 Index。


下一篇

假設 Product Page 每秒有:

100,000 Requests

即使 Database Query 已經很快,如果每個 Request 都直接打 Database:

100,000 Requests
       ↓
100,000 Database Queries

Database 還是可能成為 Bottleneck。

所以新的問題是:

如果很多 User 一直讀相同資料,真的每次都需要 Query Database 嗎?

下一篇:

Day 7|Cache:為什麼 Redis 可以讓系統快這麼多?

會開始了解:

Cache
Cache Hit
Cache Miss
Redis
TTL
Cache-Aside Pattern
Cache Invalidation

並延伸:

Cache 和 Database 不一致怎麼辦?
什麼資料適合 Cache?
Cache 掛掉會怎樣?
大量 Cache 同時失效會發生什麼?

Architecture 也會從:

Backend
   ↓
Database

進化成:

Backend
   ↓
Cache
   ↓
Database

上一篇
# Day 5|SQL vs NoSQL:System Design 到底該選哪一種 Database?
下一篇
# Day 7|Cache:為什麼 Redis 可以讓系統快這麼多?
系列文
30 天從 Full-Stack Engineer 進化到 System Design:從 0 設計可支撐百萬使用者的系統9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言