資料庫進階練習題 (Practice - Database Advanced)
答案採 >
Related Concepts
- 05-Database-Advanced/01-Transactions
- 05-Database-Advanced/02-Sharding
- 05-Database-Advanced/03-Replication
- 02-Distributed-Systems/02-CAP-Theorem
- 02-Distributed-Systems/04-Consistent-Hashing
- 02-Distributed-Systems/05-Numbers-to-Know
| 關鍵字/場景 | 對應答案 |
|---|---|
| 帳戶餘額不能為負 | ACID 的 Consistency(不是 CAP-C) |
| 庫存超賣 | Lost Update → 原子 UPDATE 或 SELECT FOR UPDATE |
| 跨服務 transaction | Saga + 補償(不是 2PC) |
is_premium 分片 |
基數太低,差勁的 shard key |
用 created_at 分片 |
寫入熱點 |
| 「全站熱門 10 篇」 | 跨 shard 查詢 → 快取 + 預計算 |
| 自己剛發貼文看不到 | Read-after-write lag → 從 primary 讀 |
| 兩個節點都以為自己是 leader | Split brain → fencing |
Transactions(Q1–Q8)
Question 1 - ACID 四字 [recall]
ACID 四個字母分別代表什麼?各用一句話解釋。
- Atomicity:所有操作全部成功或全部回滾,不會做一半。
- Consistency:DB 從一個合法狀態轉到另一個合法狀態(所有 constraint 成立)。
- Isolation:並發交易之間不互相干擾。
- Durability:已 commit 的資料在崩潰後仍存在。
Question 2 - 預設隔離等級 [recall]
PostgreSQL 的預設隔離等級是什麼?MySQL InnoDB 呢?
- PostgreSQL:Read Committed
- MySQL InnoDB:Repeatable Read
Question 3 - 異常配對 [recall]
將異常與定義配對:(1) Dirty Read (2) Non-repeatable Read (3) Phantom Read
/ (a) 同範圍查詢回傳不同數量 rows
/ (b) 讀到其他交易未 commit 的資料
/ (c) 同一 row 讀到不同的值
- 1 → (b):Dirty Read 讀到未 commit 的資料
- 2 → (c):Non-repeatable Read 是同 row 值被改
- 3 → (a):Phantom Read 是新增/刪除 row,使範圍查詢結果集變
Question 4 - Lost Update 解法 [recall]
Lost Update 是什麼?Optimistic Locking 和 Pessimistic Locking 各自如何解決?
Lost Update:兩交易同時讀同一資料、各自計算新值、都寫回,後寫者覆蓋前一個更新。
- Optimistic Locking:用版本號,
WHERE version = 原版本,不符就拒絕,application 重試。適合衝突不常發生。 - Pessimistic Locking:
SELECT ... FOR UPDATE鎖定 row,其他必須等。較安全但降低並行性、可能 deadlock。
Question 5 - 2PC vs Saga [analysis]
2PC 和 Saga Pattern 的主要差異是什麼?現代微服務更常用哪個?為什麼?
- 2PC:協調者確保所有節點同意 commit → 強一致性。問題:協調者崩潰時節點卡在「prepared 未 commit」,在高可用場景太脆弱。
- Saga:分散式交易拆成一系列本地交易 + 補償操作,用最終一致性換取高可用與容錯。
現代微服務更常用 Saga,因為 2PC 在高可用場景太脆弱。
Question 6 - ACID-C vs CAP-C [analysis]
ACID 的 C 和 CAP 定理的 C 有什麼不同?
- ACID-C:商業邏輯正確性(如餘額不能為負、constraint 必須成立)
- CAP-C:所有節點同一時間看到相同資料,是分散式副本之間的同步
完全不同概念,面試最容易混淆的點之一。
Question 7 - 超賣的正解 [application]
一個電商系統需要防止庫存超賣。為了避免並發異常,把所有庫存扣減 transaction 提升到 Serializable 隔離等級。這個做法對嗎?為什麼?
不對,過度設計。
超賣本質是 Lost Update(不是 Phantom Read),最簡單解法是原子 UPDATE:
UPDATE products SET qty = qty - 1 WHERE id = 1 AND qty > 0;
一條 SQL 搞定。如果扣減前需要複雜判斷,再用 SELECT FOR UPDATE 鎖該行即可,不需要動到隔離等級。提到 Serializable 會大幅犧牲並行性。
Question 8 - Deadlock 預防 [application]
你的 application 用了
SELECT FOR UPDATE來鎖 row。面試官問:「如果有兩個 transaction 同時鎖 account A 和 account B 但順序相反會怎樣?怎麼預防?」
會 deadlock:T1 鎖了 A 等 B,T2 鎖了 B 等 A,互相等待。
預防:所有 transaction 用一致的鎖序(例:永遠先鎖 account_id 小的)。
資料庫的 deadlock detection 會偵測並自動 rollback 其中一方,application 層需處理重試邏輯。
Sharding(Q9–Q14)
Question 9 - Partitioning vs Sharding [recall]
Partitioning 和 Sharding 的差異是什麼?
- Partitioning:在同一台 DB 內邏輯分割(相同機器、不同區段)
- Sharding:分散到不同機器(跨機器)
實務上常混用,重點是說清楚資料是在一台還是多台。
Question 10 - Shard Key 三條件 [recall]
選擇 shard key 時應考慮哪三個條件?為什麼
is_premium(布林)是糟糕的 shard key?
三個條件:
- 高基數(High Cardinality):很多不同的值
- 均勻分佈(Even Distribution):值能均勻分散
- 契合查詢模式(Aligns with Queries):常見查詢只打單一 shard
is_premium 基數只有 2(true/false),最多分 2 個 shard,無法有效分片。
Question 11 - Range vs Hash [analysis]
Range-Based 和 Hash-Based sharding 各自的優缺點?面試預設選哪個?
| Range | Hash | |
|---|---|---|
| 優點 | 支援範圍查詢 | 分佈均勻 |
| 缺點 | 容易熱點(如 created_at 全打最新 shard) |
不支援範圍、增減節點要大搬資料 |
面試預設:Hash-Based(配 consistent hashing 解決擴容問題)。
Question 12 - Celebrity Problem [application]
什麼是 Celebrity Problem?列舉兩個解決方案。
Celebrity Problem:某些 shard 因為儲存熱資料(如名人帳號)而承受不成比例的流量,成為瓶頸。
解法:
- 隔離熱 key 到專屬 shard:把名人搬到只處理名人帳號的專用 shard
- 複合 shard key:
hash(user_id + date),讓單一用戶的資料隨時間分散 - (補充)動態 shard 拆分:MongoDB balancer / Vitess online resharding
Question 13 - 跨 Shard 查詢的訊號 [analysis]
如果你發現系統需要頻繁地執行跨 shard 查詢,這代表什麼?應該怎麼應對?
代表 shard key 選錯了或 shard 邊界劃分有問題。
應對:
- 重新評估 shard key
- 反正規化:把常一起查的資料重複存到同一 shard
- 快取結果:能接受最終一致的查詢用 cache
- 背景預計算:用 batch job 預先聚合
Question 14 - 過早分片 [analysis]
一個團隊管理 800GB 的 PostgreSQL,CTO 要求工程師立刻分片。從面試官角度,這個決策合理嗎?
不合理 —— 過早分片是常見錯誤。
800GB 對單一良好調校的 PostgreSQL 太小。Sharding 之前的優化階梯都還沒走完:
- 慢查詢 → 加 index、SQL 調校
- 讀取瓶頸 → 加 cache → 加 read replica
- 寫入瓶頸 → 調參、升級硬體
觸發 sharding 的數量級(見 02-Distributed-Systems/05-Numbers-to-Know):資料 ≈ 50TiB、寫入 > 10k TPS、uncached read < 5ms 要求、跨區域、備份時間爆掉。
Replication(Q15–Q22)
Question 15 - 三種架構優缺點 [recall]
Single-Leader、Multi-Leader、Leaderless 三種架構各自最大的優點和缺點?
- Single-Leader:簡單、無寫入衝突 / Leader 是 SPOF,所有寫入都走同一節點
- Multi-Leader:跨 DC 寫入延遲低、容忍 DC 失效 / 寫入衝突難解
- Leaderless:寫入可用性高、容忍節點失效 / 一致性弱、需應用層合併
Question 16 - Read-After-Write [application]
什麼是 Read-After-Write Inconsistency?列舉兩個解法。
問題:使用者寫入後立刻讀,被路由到尚未同步的 follower → 讀到舊值。
解法:
- 讀自己改過的東西時從 leader 讀(自己看自己 profile 永遠從 leader)
- 追蹤最後寫入位置(LSN / binlog position),client 帶上 LSN,replica 必須追上才能服務
- (補充)短暫讀 primary 視窗(寫入後 N 秒內全打 primary)
Question 17 - Quorum 公式 [recall]
Leaderless 架構中,Quorum 的條件公式是什麼?n=3 時,w 和 r 通常設多少?
公式:w + r > n(讀寫節點必然有重疊)
n=3 時,常見 w=2, r=2(容忍 1 個節點失效)。
Question 18 - Failover 挑戰排除 [recall]
以下哪個不是 Single-Leader Failover 的挑戰?
(A) Split brain (B) Conflict resolution (C) 非同步複製的資料遺失 (D) Timeout 門檻設定困難
答案:(B) Conflict Resolution
Conflict resolution 是 Multi-Leader 架構的挑戰。Single-Leader 只有一個 leader 接受寫入,不會有寫入衝突。
Question 19 - 同步 vs 非同步 [analysis]
同步 replication 和非同步 replication 的核心取捨是什麼?實務常用什麼折衷?
- 同步:保證 follower 有最新(一致性),但 follower 沒回應就全部寫入卡住(犧牲可用性)
- 非同步:寫入快、可用性高,但 leader 在 follower 同步前掛了就永久遺失
折衷:Semi-synchronous(半同步) —— 一台 follower 同步、其他非同步,至少 2 份保有最新。
Question 20 - Sloppy Quorum & Hinted Handoff [recall]
什麼是 Sloppy Quorum 和 Hinted Handoff?它們的代價是什麼?
- Sloppy Quorum:原本的「家節點」連不到時,接受寫入到其他可達的節點(不在原本 n 台裡),提升寫入可用性
- Hinted Handoff:網路恢復後,臨時保管的寫入送回家節點
代價:即使 w + r > n,也無法保證讀到最新值(最新值可能在臨時保管的非家節點上)。
Question 21 - Split Brain 風險 [analysis]
為什麼 Failover 時可能出現 Split Brain?怎麼預防?
Split Brain:偵測 leader 失效不夠可靠(網路 partition、暫時延遲),兩個節點都以為自己是 leader,兩邊都接受寫入 → 資料損毀。
預防:
- STONITH(Shoot The Other Node In The Head):物理關掉舊 leader
- Fencing Token:新 leader 帶一個遞增 token,舊 leader 的寫入會被儲存層拒絕
- Consensus 演算法(Raft/Paxos):多數節點同意才能寫入,根本不會 split brain
Question 22 - Logical Replication 的價值 [analysis]
Logical (row-based) replication 相對於 WAL Shipping 的優勢是什麼?為什麼這對 CDC(Change Data Capture)很重要?
WAL Shipping 在底層位元組層次描述資料、和 storage engine 緊耦合,版本升級可能要停機。
Logical replication 用和 storage engine 分離的 row-level 格式:
- Leader / follower 可跑不同版本
- 格式容易被外部系統解析 → 可以把資料變更串流到 Kafka、search index、data warehouse
這就是 CDC 的基礎(MySQL binlog 即此方式)—— 讓你建立資料庫之外的物化視圖、即時 ETL、event sourcing 等。
Cross-Topic(Q23–Q24)
Question 23 - Sharding + Replication 組合 [analysis]
大型分散式資料庫通常同時用 sharding 和 replication。請說明它們解決什麼不同的問題,以及一個典型配置。
- Sharding 解決:單台 DB 容量 / 寫入 上限(資料超過機器、寫入吞吐超過單機)
- Replication 解決:讀取擴展 + 高可用(避免 SPOF、降低讀取延遲)
典型配置:n 個 shard,每個 shard 內部用 single-leader + 2~3 個 follower。
- 寫入:依 shard key 路由到對應 shard 的 primary
- 讀取:可以從同 shard 的 follower 讀(接受 lag)
Question 24 - 跨 Shard Transaction 為什麼難 [analysis]
為什麼分散式系統幾乎避免跨 shard transaction?如果業務上真的需要跨 shard 操作(例:兩用戶轉帳),有什麼替代方案?
為什麼難:
- 單一 DB 的 ACID 保證無法跨機器
- 2PC 提供強一致但脆弱(coordinator 崩潰會卡住)
- 大幅增加延遲(多個網路 round trip)和故障模式
替代方案(從好到差):
- 設計成完全避免跨 shard transaction(把同用戶資料放同一 shard)
- Saga + 補償操作:扣 A → 加 B;失敗就退款給 A(最終一致)
- 接受最終一致:粉絲數、計數類資料反正規化在多 shard,幾秒不一致無妨
- (最後手段)2PC:生產環境通常避免
- ACID-C ≠ CAP-C:商業 constraint vs 副本同步
- 超賣 = Lost Update,原子 UPDATE 即可(不要動隔離等級)
- 微服務跨服務交易 → Saga(不是 2PC)
- Shard key 預設 Hash + Consistent Hashing;用
created_at必熱點 - 跨 shard 查詢頻繁 = shard key 選錯訊號
- Read-after-write 解法 = 從 primary 讀 + LSN 追蹤
- Failover 三大坑:資料遺失、split brain、timeout 拿捏
- Sloppy Quorum 犧牲一致性換可用性(AP 配置)