測驗:資料庫設計與擴展策略 — 分片、複製與 ACID
共 5 題,點選答案後會立即顯示結果
1. ACID 中的「原子性(Atomicity)」是什麼意思?
2. 垂直擴展(Scale Up)和水平擴展(Scale Out)的主要區別是什麼?
3. 在資料庫複製的主從架構中,讀寫分離為什麼能提升效能?
4. Hash-based 分片策略的主要優點和缺點分別是什麼?
5. 某電商平台的訂單系統需要選擇複製策略。訂單金額必須精確、不能有延遲誤差。應該選擇哪種複製方式?
**系列**:系統設計白話文(第 5 篇,共 5 篇 — 系列最終篇)
**難度**:L2-進階
**前置知識**:【系統設計白話文】#04 快取、CDN 與流量管理
**影片來源**:[freeCodeCamp — System Design Concepts Course and Interview Prep(Hayk Simonyan)](https://www.youtube.com/watch?v=F2FmTdLtb_4)
一句話說明
資料庫是系統的「記憶」,ACID 確保資料正確,分片和複製讓資料庫撐住大流量。
這篇在講什麼?
前四篇我們從硬體、網路、協定、快取一路走來。但不管你怎麼快取、怎麼做 CDN,最終資料都要落地到資料庫。當你的使用者從 100 人成長到 1000 萬人,一台資料庫伺服器遲早會扛不住。
這篇要回答三個問題:
- 怎麼確保資料不會寫壞? → ACID
- 一台不夠怎麼辦? → 擴展策略(垂直 vs 水平)
- 資料太多怎麼切?讀取太慢怎麼分? → 分片(Sharding)與複製(Replication)
ACID:資料庫的四大保證
白話版:銀行轉帳的故事
想像你要從帳戶 A 轉 1000 元到帳戶 B。這個操作涉及兩步:
步驟 1:帳戶 A 扣 1000 元
步驟 2:帳戶 B 加 1000 元
如果步驟 1 做完、步驟 2 還沒做,系統就當機了呢?1000 元就「憑空消失」了。ACID 就是為了防止這種事發生。
四大特性一次看懂
| 特性 | 英文 | 白話翻譯 | 銀行轉帳的例子 |
|---|---|---|---|
| 原子性 | Atomicity | 全做或全不做 | 扣款和入帳要嘛都成功,要嘛都不做 |
| 一致性 | Consistency | 交易前後資料都合理 | 轉帳前後,A + B 的總額不變 |
| 隔離性 | Isolation | 同時交易互不干擾 | 你轉帳的同時,別人也在轉,彼此不會搞混 |
| 持久性 | Durability | 完成就永久保存 | 轉帳成功後就算斷電,資料還是在 |
逐個拆解
Atomicity(原子性)—— 全做或全不做
交易開始
帳戶 A: 5000 → 4000 (扣 1000)
帳戶 B: 3000 → 4000 (加 1000)
交易結束 ✅
如果中途失敗:
帳戶 A: 5000 → 5000 (回滾,當作什麼都沒發生)
帳戶 B: 3000 → 3000 (回滾)
「原子」就是「不可分割」。一個交易裡的所有操作,要嘛全部完成,要嘛全部取消。不會有「做一半」的情況。
Consistency(一致性)—— 資料始終合理
轉帳前:A(5000) + B(3000) = 8000
轉帳後:A(4000) + B(4000) = 8000 ✅ 總額沒變
不合理的情況:
轉帳後:A(4000) + B(3000) = 7000 ❌ 少了 1000 塊!
一致性確保每次交易都讓資料從「一個合理的狀態」轉移到「另一個合理的狀態」。違反業務規則的操作會被拒絕。
Isolation(隔離性)—— 交易之間互不打擾
時間線:
User X: [開始轉帳 A→B] -------- [完成]
User Y: [開始轉帳 A→C] -------- [完成]
隔離性確保:
即使 X 和 Y 同時操作帳戶 A,
兩筆交易看到的帳戶餘額不會互相干擾。
Code language: CSS (css)沒有隔離性的話,兩個人同時讀到 A 有 5000 元,各轉 3000 元出去,帳戶 A 就會變成 -1000 元。
Durability(持久性)—— 寫了就不會丟
交易完成 → 寫入磁碟 → 回覆「成功」
即使下一秒:
- 斷電 ⚡ → 重啟後資料還在
- 伺服器當機 💥 → 恢復後資料還在
資料庫不是只把資料放在記憶體裡。一旦交易完成,資料就已經安全地寫入了永久儲存。
必看懂 vs 知道就好
✅ 必看懂(面試、設計都會用到):
- 原子性:全做或全不做
- 一致性:資料前後都合理
- 隔離性:同時操作不互相干擾
- 持久性:完成就永久保存
📌 知道就好(遇到再查):
- 隔離級別(Read Uncommitted, Read Committed, Repeatable Read, Serializable)
- WAL(Write-Ahead Logging)的實作細節
- MVCC(多版本並發控制)
擴展策略:一台不夠,怎麼加?
當資料庫撐不住了,有兩種方向可以走。
垂直擴展(Scale Up):讓一台機器更強
升級前: 升級後:
┌─────────────────┐ ┌─────────────────┐
│ DB Server │ │ DB Server │
│ 4 CPU │ → │ 32 CPU │
│ 16 GB RAM │ │ 256 GB RAM │
│ 500 GB SSD │ │ 4 TB NVMe SSD │
└─────────────────┘ └─────────────────┘
白話翻譯:原本的電腦不夠快?換一台更好的。
| 優點 | 缺點 |
|---|---|
| 簡單,不用改程式 | 有硬體上限(再貴也買不到無限 RAM) |
| 資料不用搬家 | 單點故障(這台掛了全掛) |
| 不會有資料一致性問題 | 越高階越貴,價格非線性成長 |
水平擴展(Scale Out):加更多機器
擴展前: 擴展後:
┌──────────┐ ┌──────────┐
│ DB Server│ │ DB Server 1 │
└──────────┘ ├──────────┤
│ DB Server 2 │
1 台 ├──────────┤
│ DB Server 3 │
├──────────┤
│ DB Server 4 │
└──────────┘
4 台
白話翻譯:一個人搬不動?找更多人一起搬。
| 優點 | 缺點 |
|---|---|
| 理論上無上限 | 架構複雜度大增 |
| 沒有單點故障 | 跨機器的資料一致性難處理 |
| 可以用便宜的機器 | 需要決定「資料放哪台」 |
怎麼選?
使用者 < 10 萬:垂直擴展通常就夠了
使用者 > 100 萬:該考慮水平擴展了
實際上:大部分系統兩者都會用,先垂直再水平
Code language: HTML, XML (xml)分片(Sharding):把資料切開放到多台機器
分片是水平擴展的核心手段。把一張大表的資料,按照某個規則,分散到多台機器上。
一看就懂的比喻
沒有分片:
┌─────────────────────────────┐
│ 全校 3000 個學生的資料 │
│ 全部放在一個檔案櫃裡 │ ← 找資料很慢
└─────────────────────────────┘
有分片:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ A ~ H 姓氏│ │ I ~ P 姓氏│ │ Q ~ Z 姓氏│
│ 1000 人 │ │ 1000 人 │ │ 1000 人 │
└───────────┘ └───────────┘ └───────────┘
Shard 1 Shard 2 Shard 3
← 每個櫃子只需要翻 1/3 的資料
三種分片策略
1. Range-based(範圍分片)
按 user_id 的範圍切:
Shard 1: user_id 1 ~ 1,000,000
Shard 2: user_id 1,000,001 ~ 2,000,000
Shard 3: user_id 2,000,001 ~ 3,000,000
- 優點:直覺,範圍查詢效率高(例如「找 id 100~200 的使用者」)
- 缺點:容易不均勻。如果新註冊的使用者比較活躍,Shard 3 的流量會遠大於 Shard 1
2. Hash-based(雜湊分片)
hash(user_id) % 分片數 = 要放到哪個分片
hash(12345) % 3 = 0 → Shard 1
hash(67890) % 3 = 1 → Shard 2
hash(11111) % 3 = 2 → Shard 3
- 優點:資料分佈均勻
- 缺點:範圍查詢變困難(id 100~200 的使用者可能散落在不同分片),增減分片時需要大量資料搬遷
3. Directory-based(目錄分片)
維護一個對照表:
┌──────────┬─────────┐
│ user_id │ shard │
├──────────┼─────────┤
│ 12345 │ Shard 2 │
│ 67890 │ Shard 1 │
│ 11111 │ Shard 3 │
└──────────┴─────────┘
查詢時先查對照表,再去對應的分片找資料
- 優點:最靈活,可以隨意調整
- 缺點:對照表本身成為瓶頸和單點故障
三種策略比一比
| 策略 | 分佈均勻度 | 範圍查詢 | 靈活度 | 複雜度 |
|---|---|---|---|---|
| Range-based | 可能不均 | 很好 | 中 | 低 |
| Hash-based | 均勻 | 不好 | 低 | 中 |
| Directory-based | 可控 | 依設計 | 高 | 高 |
分片的挑戰
分片不是免費的午餐,它帶來這些問題:
1. 跨分片查詢(Cross-shard Query)
「找出所有超過 30 歲的使用者」
沒分片:一條 SQL 搞定
有分片:要對每個分片都查一次,再把結果合併
Shard 1 → 查詢 → 結果 A
Shard 2 → 查詢 → 結果 B → 合併 → 最終結果
Shard 3 → 查詢 → 結果 C
2. 資料重新平衡(Rebalancing)
原本 3 個分片,現在要擴展到 5 個:
舊:[Shard 1] [Shard 2] [Shard 3]
新:[Shard 1] [Shard 2] [Shard 3] [Shard 4] [Shard 5]
需要把部分資料從舊分片搬到新分片,
搬遷期間系統還要繼續運作 → 非常複雜
Code language: CSS (css)3. 跨分片的 JOIN 操作
「找出使用者和他的訂單」
如果 User 在 Shard 1,Order 在 Shard 3:
→ 無法用一般的 SQL JOIN
→ 需要在應用層自己合併
複製(Replication):同樣的資料,多存幾份
分片是把資料「切開」,複製是把資料「複製」。兩者解決不同問題。
分片(Sharding):資料 A 在機器 1,資料 B 在機器 2 → 解決「資料量太大」
複製(Replication):資料 A 在機器 1,資料 A 也在機器 2 → 解決「讀取太慢」和「可靠性」
主從架構(Primary-Replica)
寫入
使用者 ───────────→ ┌──────────┐
│ Primary │ ──── 同步 ────→ ┌──────────┐
│ (主庫) │ │ Replica 1│
└──────────┘ ──── 同步 ────→ │ (從庫) │
├──────────┤
使用者 ←── 讀取 ─── Replica 1 │ Replica 2│
使用者 ←── 讀取 ─── Replica 2 │ (從庫) │
└──────────┘
核心原則:
- 寫入:只寫到主庫(Primary)
- 讀取:從任何一個從庫(Replica)讀
- 同步:主庫的變更會同步到所有從庫
讀寫分離:為什麼有用?
大部分應用的讀寫比例大約是 80% 讀、20% 寫。
沒有讀寫分離:
所有讀 + 所有寫 → 1 台 DB → 扛不住
有讀寫分離:
所有寫 → Primary(1 台)
所有讀 → Replica(3 台分攤) → 讀取能力提升 3 倍
這就是為什麼複製可以大幅提升效能 — 因為「讀」的壓力分散到了多台機器。
同步 vs 非同步複製
| 同步複製 | 非同步複製 | |
|---|---|---|
| 做法 | 主庫等所有從庫確認才回覆「成功」 | 主庫寫完立刻回覆「成功」,背景同步 |
| 速度 | 較慢(要等) | 較快(不等) |
| 一致性 | 強一致(從庫永遠最新) | 最終一致(從庫可能短暫落後) |
| 適合 | 金融、訂單等不能出錯的場景 | 社群動態、文章瀏覽數等允許短暫延遲的場景 |
同步複製的時間線:
Client → Primary(寫入) → Replica 1(確認) → Replica 2(確認) → 回覆 Client ✅
↑ 全部確認才回覆
非同步複製的時間線:
Client → Primary(寫入) → 回覆 Client ✅
↓(背景慢慢同步)
Replica 1... Replica 2...
↑ 可能有幾毫秒到幾秒的延遲
必看懂 vs 知道就好
✅ 必看懂:
- 主從架構:一台寫,多台讀
- 讀寫分離:讀多寫少時效果最好
- 同步 vs 非同步:速度和一致性的取捨
📌 知道就好:
- Multi-Primary(多主架構):多台都能寫,衝突處理更複雜
- 半同步複製(Semi-synchronous):只等一台從庫確認
- 故障轉移(Failover):主庫掛了,自動讓從庫升級為主庫
分片 + 複製:實戰中的組合技
真實世界不會只用一種策略。分片和複製通常一起使用:
┌──────────────────────────────────────────┐
│ 應用程式 │
│ │ │
│ ┌─────────┴──────────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Shard 1 │ │ Shard 2 │ │
│ │ (A ~ M) │ │ (N ~ Z) │ │
│ └────┬─────┘ └────┬─────┘ │
│ ┌────┴─────┐ ┌────┴─────┐ │
│ │ Primary │ │ Primary │ │
│ │ Replica1 │ │ Replica1 │ │
│ │ Replica2 │ │ Replica2 │ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────────────────┘
每個分片都有自己的主從架構
→ 同時解決「資料量大」和「讀取多」的問題
Vibe Coder 檢查點
當你在看系統架構圖或讀程式碼時,看到資料庫相關的設計,確認以下幾點:
- [ ] 看到「Transaction」或「交易」→ 想到 ACID,這段操作是否需要全做或全不做?
- [ ] 看到資料庫連線設定有多個 host → 可能是讀寫分離,主庫寫、從庫讀
- [ ] 看到
shard_key或partition→ 正在做分片,注意跨分片查詢的限制 - [ ] 看到
read_replica或readonly的連線 → 讀寫分離,這些連線只能讀 - [ ] 看到「eventually consistent」→ 用的是非同步複製,資料可能有短暫延遲
系列總結:五篇串成一張知識地圖
恭喜你走到了系列最終篇。讓我們把五篇文章串起來,看看整個系統設計的全貌:
使用者發出請求
│
▼
┌──────────────────────────────────────┐
│ #01 硬體與作業系統基礎 │
│ CPU、記憶體、硬碟、程序與執行緒 │
│ → 理解「電腦怎麼跑程式」 │
└───────────────┬──────────────────────┘
│ 請求透過網路傳送
▼
┌──────────────────────────────────────┐
│ #02 網路基礎與 DNS │
│ IP、TCP/UDP、DNS 解析 │
│ → 理解「請求怎麼到達伺服器」 │
└───────────────┬──────────────────────┘
│ 伺服器收到請求
▼
┌──────────────────────────────────────┐
│ #03 API 與通訊協定 │
│ HTTP、REST、GraphQL、WebSocket │
│ → 理解「前後端怎麼溝通」 │
└───────────────┬──────────────────────┘
│ 要處理大量請求
▼
┌──────────────────────────────────────┐
│ #04 快取、CDN 與流量管理 │
│ 快取策略、CDN、負載均衡 │
│ → 理解「怎麼讓系統更快更穩」 │
└───────────────┬──────────────────────┘
│ 資料要存起來
▼
┌──────────────────────────────────────┐
│ #05 資料庫設計與擴展策略(本篇) │
│ ACID、分片、複製、讀寫分離 │
│ → 理解「怎麼安全又快速地存取資料」 │
└──────────────────────────────────────┘
Code language: PHP (php)關鍵概念一覽表
| 篇章 | 核心問題 | 關鍵概念 |
|---|---|---|
| #01 硬體基礎 | 電腦怎麼跑程式? | CPU、RAM、程序、執行緒 |
| #02 網路基礎 | 請求怎麼到達伺服器? | IP、TCP、DNS |
| #03 API 協定 | 前後端怎麼溝通? | REST、GraphQL、WebSocket |
| #04 效能優化 | 怎麼讓系統更快更穩? | 快取、CDN、負載均衡 |
| #05 資料層 | 怎麼安全快速存取資料? | ACID、分片、複製 |
下一步怎麼走?
這五篇涵蓋了系統設計的基礎知識。如果你想繼續深入,可以往這些方向探索:
- 訊息佇列(Message Queue):系統之間的非同步溝通
- 微服務架構(Microservices):把一個大系統拆成多個小服務
- 分散式系統的取捨(CAP 定理):一致性、可用性、分區容錯不可兼得
- 監控與可觀測性(Observability):怎麼知道系統是否健康
本篇重點回顧
| 概念 | 一句話總結 |
|---|---|
| ACID | 資料庫的四大安全保證:全做或全不做、資料合理、交易不干擾、寫了不丟 |
| 垂直擴展 | 換更好的機器,簡單但有上限 |
| 水平擴展 | 加更多機器,無上限但複雜 |
| 分片 | 把資料切到多台機器,解決資料量太大的問題 |
| 複製 | 同樣的資料多存幾份,解決讀取太慢和可靠性的問題 |
| 讀寫分離 | 主庫負責寫、從庫負責讀,適合讀多寫少的場景 |
系統設計不是背誦答案,而是理解每個選擇背後的取捨。沒有完美的架構,只有適合當前需求的架構。
進階測驗:資料庫設計與擴展策略 — 分片、複製與 ACID
共 5 題,包含情境題與錯誤診斷題。