SQL 跟 NoSQL 不是「你死我活」的競爭,而是「不同場景的不同工具」。 這 Unit 把這兩個陣營背後的 CS 觀念拆給你看 — ACID、JOIN、Document Model、CAP 定理 — 以及怎麼跟 AI 一起選對工具。
先看高層差異,再進細節 — 你才知道後面 §2-§6 在拆什麼。
把資料切成多張 表、表跟表之間用 id 互指、有強保證(ACID)。 為「正確性 > 彈性」而設計 — 適合「絕對不能錯」的場景。
把「會一起讀」的資料整坨包成一份 JSON document、結構自由、讀超快。 為「彈性 > 強保證」而設計 — 適合「結構常變、讀取量爆炸」的場景。
下面 §2-§6 把這兩個陣營背後的 CS 觀念拆開: 關聯式怎麼運作(§2)、非關聯式怎麼運作(§3)、ACID 怎麼保證(§4)、JOIN 怎麼拼(§5)、結構彈性的代價(§6)。
→ 下面 §2 先進入 SQL 陣營 — 關聯式資料庫到底怎麼工作。
上一段給了兩陣營高層比較,這段進入 SQL 陣營背後的設計 — 關聯式模型(1970 年 Codd 提出)。
SQL 的底層哲學叫「關聯式模型」。意思是:把資料切成多張 表(table),每張表只描述一種東西(用戶、訂單、商品…),表跟表之間用 id 互相指。
users.id。不能重複、不能空。orders.user_id 指向 users.id。資料庫會幫你檢查,被指的對象一定要存在。這樣切表的好處:同一個用戶買 100 次,名字只存一份;商品改價,所有訂單自動拿到新價。代價是要看「小明買過什麼」必須把 3 張表「連起來」 — 這就是下一段的 JOIN。
滑鼠移到中間 orders 任一筆 — 兩條 FK 箭頭會即時畫出來,指向 users 跟 products 的對應列。
| id | name | |
|---|---|---|
| u1 | 小明 | a@x.com |
| u2 | 小華 | b@x.com |
| id | user_id | product_id | qty |
|---|---|---|---|
| o1 | u1 | p1 | 2 |
| o2 | u2 | p1 | 1 |
| o3 | u1 | p2 | 3 |
| id | name | price |
|---|---|---|
| p1 | 拿鐵 | 120 |
| p2 | 可頌 | 80 |
想像一張表把所有東西都塞進去 — 每筆訂單除了 order_id、quantity,還重複存著作者名字、email、商品名稱、價格。 看起來方便(一張表就拿到所有資訊),但有個大問題:同一份資料重複很多份。小明改 email,你要找出所有他的訂單一筆一筆改。漏改一筆 → 資料不一致 → bug 來了。
Normalization(正規化) 就是把重複資料拆出來:作者放 users 表、商品放 products 表、訂單只存 id 連回去。改名字只要改一個地方,所有訂單自動「看到新名字」。
但讀的時候就要 JOIN 連回去(下一段的事)— 寫得快、讀得稍慢。 反過來「為了讀快、故意重複存」叫 Denormalization(反正規化),是有意識的取捨,不是疏忽。
下面兩個按鈕讓你體感差別 — 同樣是「把小明改名」,扁平表要改 3 筆訂單;正規化表只要改 users 表的一筆。
| order | author | product | |
|---|---|---|---|
| o1 | 小明 | ming@x.com | 拿鐵 |
| o2 | 小華 | hua@x.com | 拿鐵 |
| o3 | 小明 | ming@x.com | 可頌 |
| o4 | 小明 | ming@x.com | 拿鐵 |
| u1 | 小明 | ming@x.com |
| u2 | 小華 | hua@x.com |
→ 切表的代價是要「拼回來」— §5 會講 JOIN;但 NoSQL 是相反的取捨 — §3 先看它。
跟 SQL 對立的哲學 — 不切表,整坨包成 document。作者改名 — NoSQL 要動 12 筆、SQL 只動 1 筆。
NoSQL 文件式資料庫(Firestore、MongoDB)的哲學跟 SQL 相反:不切表,而是把「會一起被讀」的資料整坨包成一份 JSON 文件存起來。 要顯示一個 IG post?整筆 document 一次取出來 — 不用 JOIN、不用先抓 A 再抓 B。
優點:讀取超快、結構可以不一樣(每筆 document 自由)、新欄位直接寫進去不用 ALTER TABLE。
代價:相同資料會「重複存在多筆 document 裡」 — 每篇 post 都存著作者名字。 如果作者改名 → 你要批次更新所有他的 post。這就是 SQL(更新方便)vs NoSQL(讀取方便)的核心取捨。
當資料庫只跑在一台機器上,沒有這個問題。但只要 分散到多台(為了高可用、為了擴展),就會撞上分散式系統的鐵則 — CAP 定理。
這三個 最多只能同時有兩個。實務上 P 是「現實」(網路本來就會斷),所以真正的選擇是 CP(保一致犧牲可用) 還是 AP(保可用犧牲一致)。
→ Document Model 讀超快,但失去 SQL 的兩個保證:ACID 跟 JOIN。§4 先看 ACID 是什麼。
上一段 NoSQL 為了讀取速度犧牲 ACID — 那 ACID 到底是什麼?按按鈕看四種「壞掉的轉帳」,每種對應 ACID 少了一個字母。
想像你按下「轉 $100 給朋友」 — 銀行系統要做兩件事:你的帳戶 -$100、朋友的帳戶 +$100。 如果中間出任何狀況(App 當掉、停電、餘額不夠、兩人同時轉),錢都不能憑空消失或多出來。 資料庫提供的這四個保證合起來就是 ACID:
這四個保證打包在一起叫 Transaction(交易)。SQL 資料庫天生支援、NoSQL 看廠商支援程度。
→ ACID 保住單一資料庫內的正確性,但跨表查詢還需要 JOIN — §5。
§2 把資料切多張表,這段把表拼回來 — 切換 4 種 JOIN 看「誰留下、誰被丟掉」。
§2 把資料拆成多張表,但實際要用時你還是要看到「誰買了什麼」 — 這就需要把表 拼起來,叫做 JOIN。 四種 JOIN 寫法的差別只在一件事:對不上的列要不要留?
下面這份資料故意設計兩個邊界 case:「小美」沒下過任何單、「舊訂單」的用戶已經被刪掉 — 切換 4 種 JOIN,你會直接看到誰被留下、誰被丟掉。
| user | item | 這列的命運 |
|---|---|---|
| 小明 | 拿鐵 | ✓ 配對成功,留下 |
| 小華 | 可頌 | ✓ 配對成功,留下 |
| 小明 | 美式 | ✓ 配對成功,留下 |
| 小美 | NULL | ✗ 丟掉(users 有,沒下過訂單) |
| NULL | 舊訂單(用戶已刪) | ✗ 丟掉(orders 有,用戶被刪了) |
SELECT users.name, orders.item FROM users INNER JOIN orders ON users.id = orders.user_id
→ JOIN 是 SQL 的招牌;但 NoSQL 不切表,所以也沒 JOIN — 它怎麼處理結構變化?§6。
SQL 嚴格 schema 跟 NoSQL 彈性 schema 改起來代價差很多。產品快速迭代時加欄位是日常 — 方便有代價,只是「方便」被推到哪去而已。
隨著產品功能長大,你常需要「給資料表加新欄位」。SQL 跟 NoSQL 對這件事的處理方式天差地遠。
SQL(嚴格 schema):必須先「宣告」這個欄位存在(ALTER TABLE),歷史資料要回填預設值。大表的 ALTER 可能 鎖表幾分鐘到幾小時 — 上線時整個 App 寫不進去。所以實務上要排時段、寫 migration script、先在測試環境量測。
NoSQL(彈性 schema):直接 db.posts.add({ ..., ar_filter: "..." }) 就好 — 新 document 有這欄、舊的沒有,DB 不管你。 看起來方便對吧?但代價來了:你的程式碼要自己處理「有的有、有的沒有」。 每讀一筆都要寫 post.ar_filter ?? "default"。debug 時你會很懷疑人生。
這就是「方便」的代價 — 不是消失,是 推給了應用層。下面兩個按鈕讓你體感差別。
ALTER TABLE 不提鎖表時間。小表沒事,但 100 萬筆以上的大表 ALTER 可能鎖數分鐘到數小時 — 整個 App 寫不進去。→ 兩陣營背後的 CS 觀念都看完了 — §7 把它們套用到 5 個實際場景做選擇練習。
§1-§6 給了概念,這段把所有概念回答到 5 個具體場景 — 從 SQL / NoSQL / Key-Value 三個選項挑一個,立即知道對錯 + 為什麼。
選資料庫不是先看名字(PostgreSQL? Firebase?),而是先看 場景特性。 不同需求對應到不同陣營:要保證「絕不能錯」的用 SQL(關聯式);要保存「結構不固定的大量資料」的用 NoSQL(文件式);要極致速度的用 Key-Value 快取。
下面 5 題每題給你一個場景跟 3 個選項。先憑直覺答 — 答錯沒關係,答完整個 Unit 後再回來重做一次,你會發現答案變得很自然。
知道 SQL / NoSQL / KV / Vector 四個陣營後,下一層是「每個陣營有哪些代表產品」。 你不用背特性,但要看到名字能反應出「它大概解什麼問題、適合什麼場景」。
這樣跟 AI 對話時,你聽到它推薦 Firestore,能反問「複雜查詢怎麼辦?」;它推薦 Postgres,你能反問「向量搜尋要不要直接用 pgvector?」 — 這就是「看得懂 AI 給的建議、能往下追問」的能力。
你要做一個轉帳系統,需要 ACID 保證、強一致性。AI 應該推薦哪個?
你要做即時協作聊天 App,訊息要「資料變了 client 自動收到」。AI 預設會推哪個?
你要做一個 AI 客服 + RAG 語意搜尋,剛起步、規模小。AI 應該推哪個?
你要做 API 限流(每個 user 每分鐘最多 100 次),計數要超快。AI 推哪個?
你是 vibe coder,想要 PostgreSQL 的所有能力 + 不想自己架 server + 還要附 Auth。AI 推哪個?
→ 最後看 AI 時代最常見的後端架構 — §8。
從第一個需求開始,看著資料模型一個個長出來 — 跟 AI 講需求的人 vs 跟 AI 拷貝範例的人,差距在這。
ChatGPT 類產品千變萬化,但後端資料模型有 80% 共通的長相 — 不是巧合,是「需求一個個加上去,自然會逼出這幾張表」。 下面用一個 vibe coder 的開發直播帶你跑一遍 — 每加一個需求,就有一個資料模型解問題。 看完你就明白:這不是「規格清單」,是「逼到牆角後的自然解」。
看出來了嗎?6 個表分別住在 4 種儲存(SQL / Vector / Redis / S3) — 不是設計師耍帥,是 每種需求都有最匹配的工具。 工程界叫這個 polyglot persistence(多語言持久化)。但你不需要記名詞,只要記住一句話:單一資料庫不可能滿足所有需求。
下次跟 AI 講「幫我做一個 ChatGPT clone」,不要等 AI 給你方案 — 你 主動帶出這 4 個需求,問:
➀「對話跟訊息要分開兩張表,對嗎?」
➁「embeddings 第一天就用 pgvector 還是先用 jsonb 欄位?」
➂「rate limit 走 Redis 還是 PostgreSQL?」
➃「檔案放 S3,DB 只存 metadata,對嗎?」
四題答完,AI 給你的方案會精準到 可以直接動手寫。
→ 走完 8 段,你已經有 vibe coder 跟 AI 對話的全套後端直覺。§9 收尾把整個 Unit 收口。
SQL vs NoSQL 不是「哪個比較好」,
是「在這個場景哪個比較適合」。
跟 AI 討論架構時可以直接用的 7 個直覺:
知道選哪個陣營之後,下一步是學會「畫出你的 App 的資料地圖」 — 怎麼跟 AI 一起把 schema 設計好,少踩遷移地獄。