Database (SQL vs NoSQL)
my-notes/system-design-hld/concepts · ZH
💡 一、資料庫的兩大主流類型
現代系統主要分為兩大資料庫家族:
-
SQL(Relational Databases)關聯式資料庫
-
NoSQL(Non-relational Databases)非關聯式資料庫
兩者的核心差異在於 資料結構、查詢語法、一致性與擴展策略。
🧩 二、SQL vs NoSQL 對照表
| 面向 | SQL(Relational) | NoSQL(Non-relational) |
|---|---|---|
| 資料結構 | 表格(Table)+ 關聯(Foreign Key) | 文件、鍵值、圖形或寬欄 |
| Schema | 固定結構(定義欄位) | 彈性結構(可動態欄位) |
| 查詢語言 | SQL(結構化查詢語言) | 各自定義(JSON / API) |
| 一致性模型 | ACID(強一致) | BASE(最終一致) |
| 可擴展性 | 垂直擴展(Scale-Up) | 水平擴展(Scale-Out) |
| 交易支援 | 強交易支援(Transaction) | 多為弱交易或最終一致 |
| 適用場景 | 金融、交易、報表 | 高併發、快取、文件儲存 |
| 代表系統 | MySQL, PostgreSQL, Oracle | MongoDB, Redis, Cassandra |
⚙️ 三、ACID(SQL 資料庫的核心)
ACID 是 SQL 資料庫確保資料正確性的四大原則:
| 項目 | 說明 | 範例 |
|---|---|---|
| Atomicity | 原子性:交易要嘛全成,要嘛全退 | 轉帳兩步操作不可分開 |
| Consistency | 一致性:交易後資料仍符合法則 | 餘額不能負數、外鍵存在 |
| Isolation | 隔離性:交易間不互相干擾 | 同時查詢不應讀到未提交資料 |
| Durability | 持久性:交易成功即永久保存 | 系統崩潰後仍可恢復 |
💡 ACID 適用於對資料正確性要求極高的系統: 銀行帳戶、訂單處理、會計報表等。
🧭 四、SQL 資料庫代表系統
🐬 MySQL
- 最普及的開源 RDBMS。
- 支援強一致性、交易、索引與 Join。
- 可使用 InnoDB 引擎(支援 ACID)。
- 適合:電商後台、金流、傳統 Web 系統。
🐘 PostgreSQL
- 更嚴謹的 SQL 標準支援與強型別系統。
- 原生支援 JSON、全文搜尋、GIS。
- 適合:分析報表、企業級應用、金融系統。
- 特點:可作為 OLTP + OLAP 混合型資料庫。
🏛️ MySQL vs PostgreSQL 比較
| 面向 | MySQL | PostgreSQL |
|---|---|---|
| 一致性 | 強一致(InnoDB) | 強一致(WAL) |
| 查詢語法 | 簡潔、普及 | 標準更完整 |
| JSON 支援 | 有,但性能較弱 | 原生 JSONB 類型 |
| 擴充性 | 生態豐富 | 可自定義類型與函式 |
| 適用場景 | 高流量網站 | 數據分析與企業應用 |
🧩 五、NoSQL 資料庫代表系統
🍃 MongoDB(文件導向)
- 資料以 JSON / BSON 文件格式儲存。
- 結構彈性(Schema-less),易於快速開發。
- 支援索引、聚合、Replica Set(具備最終一致性)。
- 適合:用戶資料、動態內容、內容管理系統。
⚡ Redis(鍵值導向)
- 基於記憶體的高效快取系統。
- 支援 String、List、Set、Hash、Sorted Set。
- 延伸用途:Session 儲存、Queue、Rate Limiting。
- 支援簡單交易(MULTI/EXEC),但非完整 ACID。
- 適合:快取、排行榜、分散式鎖(Distributed Lock)。
🧮 六、SQL 與 NoSQL 的取捨思維
| 系統設計需求 | 優先考量 | 建議選擇 |
|---|---|---|
| 交易一致性(金融) | 資料正確 | SQL(MySQL, PostgreSQL) |
| 高併發、高可用 | 效能 / 擴展 | NoSQL(Redis, MongoDB) |
| 動態結構資料 | 彈性 Schema | NoSQL(MongoDB) |
| 複雜查詢與報表 | Join、Aggregation | SQL |
| 瞬時資料 / 快取 | 低延遲 | Redis |
🔒 七、資料庫鎖機制(Locking)
鎖的目的是防止同時修改同筆資料導致不一致。
依照場景分為:悲觀鎖(Pessimistic)、樂觀鎖(Optimistic)、分散式鎖(Distributed Lock)。
1️⃣ 悲觀鎖(Pessimistic Lock)
- 假設「一定會衝突」,先鎖再操作。
- 通常在交易開頭就鎖住資料列,其他人要等。
- 代表語法:
SELECT * FROM users WHERE id=1 FOR UPDATE;
- 適合高衝突場景(如銀行轉帳)。
- 缺點:效能較低,會造成等待。
🧠 比喻:
「我先把門鎖上再工作,其他人只能等我做完。」
2️⃣ 樂觀鎖(Optimistic Lock)
- 假設「大多數情況不會衝突」,只在更新前檢查版本。
- 常見實作:version 欄位 + CAS(Compare And Swap)
UPDATE users SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = 5;
- 若 version 不符,代表資料已被修改 → 更新失敗需重試。
- 適合低衝突、高併發的讀多寫少場景。
🧠 比喻:
「我不鎖門,但更新前先確認別人沒動過。」
3️⃣ 分散式鎖(Distributed Lock)
當系統有多台應用伺服器同時存取共用資源(例如 Redis、DB、檔案), 就需要跨節點協調鎖機制。
常見方案:
| 機制 | 說明 | 代表工具 |
|---|---|---|
| Redis-based Lock | 透過 SETNX(set if not exists)實現 | Redlock(官方演算法) |
| Zookeeper Lock | 使用 ZNode 建立臨時節點作為鎖 | Apache Curator |
| Database Lock | 利用唯一索引或 SELECT FOR UPDATE 模擬鎖 | MySQL / PostgreSQL |
Redis 分散式鎖範例:
SET resource_lock myid NX PX 30000
# NX = 不存在時才設定
# PX = 鎖過期時間(ms)
🧩 注意事項:
- 分散式鎖需避免死鎖(lock timeout 機制)
- Redis 節點故障可能導致鎖失效 → 可搭配 Redlock / quorum 設計
🧠 八、總結
鎖的比較
| 項目 | 悲觀鎖 (Pessimistic) | 樂觀鎖 (Optimistic) |
|---|---|---|
| 概念 | 操作前先鎖 | 操作後再檢查 |
| 實現方式 | SELECT ... FOR UPDATE | 比對 version 或 timestamp |
| 效能 | 低(需等待鎖釋放) | 高(無阻塞) |
| 一致性 | 強一致 | 最終一致(需重試) |
| 適合情境 | 高衝突(搶購、交易) | 低衝突(一般更新) |
| 死鎖風險 | 有 | 無 |
| 實現難度 | 簡單 | 較複雜(需應用層控制) |
實務設計建議
| 系統類型 | 建議用法 |
|---|---|
| 銀行轉帳、錢包餘額 | 悲觀鎖(保證一致性) |
| 用戶設定、個人資料編輯 | 樂觀鎖(平行度高) |
| 商品庫存(高併發搶購) | 一般用悲觀鎖或 Redis 分布式鎖 |
| 後台資料編輯 | 樂觀鎖 + 更新失敗提示(如 Git Merge 概念) |
distributed lock
在多台服務(多進程/多容器)同時操作同一份資源(庫存扣減、排程任務、報表產生、上鏈簽名)時,需要一個跨節點的互斥機制,避免併發衝突與重覆執行。
| 重點 | SQL | NoSQL |
|---|---|---|
| 一致性 | ACID(強一致) | BASE(最終一致) |
| 結構 | 嚴格 Schema | 彈性結構 |
| 查詢能力 | Join / Transaction | 高速簡化操作 |
| 擴展 | 垂直為主(水平也有辦法) | 水平為主 |
| 鎖機制 | 內建悲觀 / 樂觀鎖 | Redis、Zookeeper 實現分散式鎖 |
| 使用時機 | 金融、交易、報表 | 高併發、快取、非結構化資料 |
⚡ SQL vs NoSQL 效能比較與擴展策略說明
🧠 一、哪個效能更好?(這題沒有絕對答案)
✅ 結論先講: 在單機環境下,SQL 通常更快。 在大規模分散式環境下,NoSQL 通常更能撐高併發與吞吐。
兩者的效能差異,不在語法本身,而在於:
- 資料結構
- 查詢方式
- 一致性模型
- 擴展架構(vertical vs horizontal)
⚙️ 二、效能差異的本質原因
| 項目 | SQL(Relational) | NoSQL(Non-relational) |
|---|---|---|
| 資料組織方式 | 多表 + 關聯(JOIN) | 扁平結構(Document / Key-Value) |
| 查詢模式 | 複雜查詢 + JOIN | 直接取用單筆或集合 |
| 交易一致性 | 強一致(ACID) | 弱一致(BASE) |
| 讀寫特性 | 單機高效、複雜邏輯 | 分散式高吞吐、簡化邏輯 |
| 最佳場景 | 報表、金融交易、分析 | 快取、即時服務、內容動態 |
👉 換句話說:
- SQL:更適合「複雜查詢」與「強一致」的應用。
- NoSQL:更適合「高併發、簡單讀寫」的應用。
🔬 三、實際效能比較案例
| 操作場景 | MySQL / PostgreSQL | MongoDB / Redis |
|---|---|---|
| 單筆查詢(索引命中) | 約 1~5 ms | 約 1 ms(Redis <1ms) |
| 大量 JOIN / 聚合分析 | SQL 表現優秀(可用索引、視圖) | MongoDB 較慢、Redis 不適合 |
| 高併發寫入(>10k ops/s) | 難以線性擴展(磁碟 I/O 限制) | 水平擴展可線性提升(Sharding) |
| 交易一致性(轉帳) | 完整 ACID | 須額外實作一致性機制 |
| 快取(Session, Token) | 效能普通 | Redis 極快(in-memory) |
💡 總結:
-
Redis:效能最高,但資料在記憶體,適合快取與分散式鎖。
-
MySQL/Postgres:單節點性能強,但難撐高併發寫入。
-
MongoDB:結構彈性好,水平擴展容易,效能取決於資料設計。
🧩 四、SQL 為什麼是「垂直擴展(Vertical Scaling)」為主?
🧱 1️⃣ 定義
| 類型 | 說明 | 圖像比喻 |
|---|---|---|
| 垂直擴展 (Scale Up) | 升級單台伺服器(CPU、RAM、SSD) | 🚀「讓一台機器更強」 |
| 水平擴展 (Scale Out) | 增加多台伺服器分攤負載 | 🌐「讓更多機器分工合作」 |
⚙️ 2️⃣ 為什麼 SQL 傾向垂直擴展?
SQL 資料庫的關聯模型(JOIN、Transaction、ACID) 需要全域一致性與跨表操作,這導致它不容易拆分成多節點。
原因如下:
- JOIN 操作必須在同一資料節點進行 → 一旦跨節點,JOIN 成本暴增。
- Transaction 要保證原子性 → 需跨表鎖定與同步,跨伺服器時很難維持 ACID。
- 索引(B-Tree)為中心化結構 → 拆分會失去全域排序與唯一性檢查。
- 一致性模型偏向 CP(Consistency + Partition Tolerance) → 在分散式環境下,通常選擇「暫時不可用」而非「最終一致」。
💡 所以:
SQL 適合「單機高效能 + 強一致性」的設計, 當資料成長時,會先「Scale Up」再考慮「Read Replica / 分片」。
🧮 3️⃣ 而 NoSQL 為什麼容易「水平擴展」?
NoSQL(特別是 Document / Key-Value 型)設計天生具備:
- 資料之間關聯鬆散(無 JOIN)
- 操作通常針對單筆 key(簡單查詢)
- 一致性採 BASE(最終一致)
這讓它能:
把資料**平均分散(Sharding)**到多台機器, 每台節點都能獨立處理請求。
例如:
- MongoDB → 自動分片(Shard Key)
- Cassandra → Hash Partition
- Redis Cluster → Slot-based 分配
🧠 五、真實世界的策略:混合使用
現代大型系統幾乎都採用「SQL + NoSQL 混合架構」
| 資料類型 | 儲存系統 | 理由 |
|---|---|---|
| 用戶資料、訂單 | MySQL / PostgreSQL | 保證交易一致性 |
| 快取、Session、排行榜 | Redis | 極速讀取與過期機制 |
| 使用者動態、訊息流 | MongoDB / Cassandra | 彈性 Schema、高寫入 |
| 日誌 / 分析事件流 | Elasticsearch / BigQuery | 高速查詢與聚合 |
💬 範例:
Spotify 使用 PostgreSQL 儲存用戶與訂閱資料, 使用 Cassandra 儲存播放紀錄, 使用 Redis 做快取與即時排行榜。
⚖️ 六、總結比較
| 面向 | SQL | NoSQL |
|---|---|---|
| 結構 | 固定 Schema(表格) | 彈性 Schema(JSON、Key-Value) |
| 一致性 | 強一致(ACID) | 最終一致(BASE) |
| 擴展方式 | 垂直擴展為主 | 水平擴展為主 |
| 效能瓶頸 | 單機 I/O 限制 | 網路延遲 / 資料分佈 |
| 查詢能力 | JOIN、聚合強 | 單表快、無 JOIN |
| 最適應用 | 金融、交易、報表 | 快取、動態內容、高併發 |
| 維運難度 | 結構穩定、操作複雜 | 分片管理、同步機制複雜 |
| 延展性 | 低(Scale-Up) | 高(Scale-Out) |
✅ 七、一句話總結
-
SQL 追求「資料正確」 → 單機高效但難分散。
-
NoSQL 追求「系統穩定」 → 分散容易但一致性弱。
🚀 小規模 → SQL 通常更快 🌐 大規模、高併發 → NoSQL 更能撐負載
所以,效能高低不是語法決定的,而是 取決於資料特性、系統規模與一致性需求。