兩種主流資料模型。文件模型用巢狀結構換取局部性 (locality) 與「一對多」的自然嵌套;關聯模型用正規化與 join 把「多對多」表達得乾淨。選哪個,本質是在問你的資料「關係長什麼樣、查詢怎麼進來」。
🧠 Intuition
TIP
一句話分辨:資料像一棵樹(一份履歷、一張訂單連同明細)→ 文件模型;資料像一張網(人連到公司、公司連到城市、彼此互相引用)→ 關聯模型。模型選錯,就是天天在跟資料的形狀對抗。
- 物件-關聯阻抗不匹配 (impedance mismatch):應用層是物件樹,關聯庫是扁平的表,中間要靠 ORM 來回翻譯——這層摩擦正是文件模型想消滅的。
- 文件模型:一筆資料 = 一份自包含的 JSON/BSON 文件,巢狀結構直接對應物件樹,讀一份就拿到全部,不用 join。
- 關聯模型:資料拆成正規化的多張表,用外鍵與 join 在查詢時重組;重複資料少、一致性好維護。
- 兩者近年互相靠攏:關聯庫加了 JSON 欄位,文件庫加了 join 與多文件交易——但預設長處仍在各自原點。
⚖️ Tradeoffs
局部性 (Locality) — 文件模型的主場
- 一份文件存成連續一塊,讀整份(如整張訂單)只要一次存取,省掉多表 join 的隨機 I/O。
- 但局部性是雙面刃:通常得讀整份文件才能取其中一小段,文件越大越浪費。
- 更新也常需重寫整份文件(只有「不改變大小」的原地改才便宜)。
- 結論:當應用「一次就要載入整棵樹」時,局部性是實打實的效能優勢。
一對多 — 文件自然嵌套,關聯要拆表
- 一份履歷有多段學歷、多個職位、多項技能——這是天然的一對多樹狀結構。
- 文件模型:直接把它們當陣列嵌在文件裡,結構即資料、所見即所得。
- 關聯模型:得拆成 positions、educations 等子表,用外鍵連回主表,查詢時再 join 回來。
- 對「一對多 + 整棵一起讀」的場景,文件模型語意更貼、程式更短。
多對多與 join — 關聯模型的主場
- 一旦出現多對多(推薦人連到別的使用者、貼文被多人標註),文件模型只能存「引用 (reference)」,再在應用層手動 join。
- 應用層 join 不但程式碼複雜,還少了查詢優化器、index 等資料庫級別的支援。
- 關聯模型的 join 是一等公民,多對多、遞迴關係都交給查詢優化器處理。
- 關係越「網狀」、join 越多,關聯模型(或圖模型)越划算。
schema-on-read vs schema-on-write
- 文件庫多為 schema-on-read:寫入不檢查結構,讀取時由應用程式解讀——彈性高、適合異質或常變的資料,但錯誤往讀取時延後爆發。
- 關聯庫多為 schema-on-write:寫入時強制檢查 schema,像靜態型別——一致性強,但改 schema 要 migration。
- 關鍵差異在「結構的隱性 vs 顯性」:文件庫的 schema 其實藏在應用程式碼裡,只是沒寫進資料庫。
- 欄位高度異質、或迭代極快的早期專案 → schema-on-read 較順手;資料契約穩定、多方共用 → schema-on-write 較安全。
🔑 Takeaways
- 資料像樹選文件模型(局部性 + 一對多自然嵌套),像網選關聯模型(多對多 + join)。
- 文件模型用局部性換效能,代價是讀寫常需處理整份文件、且多對多得在應用層手刻。
- schema-on-read(文件,彈性)對上 schema-on-write(關聯,一致性)——差別在 schema 是藏在程式裡還是寫進資料庫。
- 物件-關聯阻抗不匹配是文件模型的初心,但別忘了多數真實資料同時有樹也有網。
2026-07-01
- Q: 既然文件模型「讀整份很快」,為什麼不一律用它?
- A: 因為它快的前提是「資料是樹、且你真的要整棵讀」。一旦關係變成網狀(多對多、跨文件引用),文件模型就退化成在應用層手刻 join,失去資料庫優化器的所有幫助,且容易出現資料不一致。它的局部性優勢也會反咬——文件越長大,每次讀寫整份的成本越高。所以選型要看資料的關係形狀,而非單純的讀寫速度。