MongoDB Schema 設計系列 (二):Entity 關聯與設計模式選擇
在上一篇中,我們介紹了什麼是 Data Modeling 還有基本工作流。在這篇文章中,我們將深入探討不同 Entity(實體)之間如何建立關聯,以及在不同關聯關係下,選擇不同設計模式的權衡與優缺點。
為了方便理解,接下來的內容我會以日常生活中最常見的情境來舉例 —— 網購平台。
在網購系統中,使用者 與 訂單 是兩種不同類型的資料實體,它們之間存在著緊密的關聯。接下來我們就透過這個情境,來看看兩種核心的關聯方式。
首先,在 MongoDB 中,Entity 之間的關聯主要有兩種實作途徑:
- Embedding(嵌入)
- Referencing(參照)
Embedding (嵌入)
Embedding 指的是將關聯資料直接「嵌入」在父文件內,形成巢狀的 Document 結構。
例如:每一筆訂單都直接包含買家的詳細資料。這時我們將使用者資料嵌入訂單資料這個做法,就稱之為 Embedding。

使用 Embedding 的優缺點
- 優點(開發效率與讀取效能): 讀取非常方便。當我們查詢訂單資料時,買家的資料會一併載入,不需要額外執行第二次查詢去撈使用者資料,也沒有 SQL 式的 Join 效能損耗。
- 缺點(資料重複與一致性成本):
會造成資料的重複儲存。如果一個使用者在平台上有十筆訂單,該使用者的基本資料就會被複製並嵌入到這十筆訂單中。這不僅降低了磁碟空間的利用率,更增加了維護「資料一致性」的成本。
維持資料一致性的代價:例如當使用者更換電話號碼時,所有尚未結案的訂單紀錄都必須同步更新電話號碼。
如何評估「資料重複」是否值得?
我們可以從以下三個面向來評估是否該使用 Embedding:
- 資料的「快照」特性是否正是你想要的? 以送貨地址為例,雖然使用者有預設地址,但一旦訂單成立,該次交易的送貨地址就應該被「定格」在訂單中。即使使用者事後修改了帳號的預設地址,歷史訂單的送貨地址也絕不能改變。在這種情境下,Embedding 就是最完美的選擇。
- 這兩種類型的資料,在應用程式中是否極為常伴隨出現? 在展示訂單詳細頁面時,是否幾乎 100% 都需要顯示買家的聯絡資訊?如果「讀取/寫入/編輯」訂單的動作在系統中佔了極高比例,且總是伴隨著買家資料,那麼根據「常在一起的資料放一起」原則,應優先選擇 Embedding。
- 維持資料一致性的更新頻率與成本是否可以接受? 被嵌入的資料變動頻率高嗎?需要即時同步嗎?如果一項資料變動非常頻繁(例如使用者的即時線上狀態),且每次變動都要求所有嵌套文件同步更新,那硬體資源(I/O 與 CPU)的消耗將會非常劇烈。
Referencing (參照)
Referencing 是將關聯文件的唯一識別值(如 _id)存放在另一個文件的欄位中,透過這個 ID 來建立跨文件的引用與參照。
例如:訂單的 buyer 欄位只儲存使用者的 _id。當我們需要完整的買家資訊時,再透過這個 ID 前往 users 資料表查詢。

使用 Referencing 的優缺點
- 優點(低重複、易一致):
資料重複率極低,沒有資料冗餘的問題。當使用者修改電話號碼時,只需要更新
users資料表中的一筆 Document 即可,完全沒有一致性同步的負擔。 - 缺點(多次查詢與孤兒資料):
當需要完整資料時,必須進行額外的讀取(Lookup/Population)操作。此外,如果刪除了某位使用者,我們必須額外處理剩餘訂單中的
buyer_id指針,否則會殘留指向空地址的無效參照(Dangling Reference)。
Embedding 與 Referencing 的黃金法則
在 MongoDB 中,並沒有絕對完美的設計,一切都取決於應用場景的讀寫比例。但這裡有一個實務上的經驗法則(Rule of Thumb):
如果兩種類型的資料極為頻繁地被同時讀取,優先選擇 Embedding;反之,若兩者經常獨立存在、或 one 端的資料量龐大且變動頻繁,則選擇 Referencing。
此外,還有兩個重要的工程考量:
- 16MB 文件上限限制: MongoDB 對單一 Document 的大小限制為 16MB。如果無限度地將子資料嵌入父文件中(例如,將使用者一輩子的所有點擊日誌全部 embedded 進 user document),會造成文件爆掉、無法寫入,甚至嚴重拖慢查詢效能。
- Simplicity Over Complexity: Referencing 帶來的應用層關聯與連帶刪除邏輯較為複雜。在產品開發初期,如果關係的數量級可控,可以先從 Embedding 開始以加快開發進度,等後期遇到效能瓶頸或資料量膨脹時,再重構為 Referencing。
1 to 1、1 to Many、Many to Many 的設計權衡
有了這兩種關聯方式後,我們來看看在不同的關係模型下,該如何做出抉擇:
1. 1 to 1 關係
- 例子:學號與學生,一位學生只會擁有一個學號。
- 設計:絕大多數情況下直接使用 Embedding。至於誰嵌入誰,取決於哪一個 Entity 被查詢的次數最頻繁。
2. 1 to Many 關係
- 例子:一個人可以擁受多張信用卡,但一張信用卡只屬於一個人。

在 1 to Many 的關係中,我們有以下四種設計策略:
策略 A:將 1 的資訊嵌入到 Many 這端 (Embedding in Child) 在每張信用卡的 Document 中,直接存入持有者的基本資料。
- 適用場景:查詢多端(信用卡)的頻率遠高於單端,且能容忍使用者資料變更時的同步成本。

策略 B:將 Many 的資訊嵌入到 1 這端 (Embedding in Parent) 將使用者所擁有的信用卡完整資訊,以 Array 形式儲存在使用者 Document 中。
- 重要提醒:務必注意 Many 的數量級上限。信用卡數量一般很少,嵌入很安全;但如果是「文章與留言」的 1 to Many,留言數量可能成千上萬,嵌入就會面臨 16MB 的上限威脅。

策略 C:1 透過 Referencing 參照 Many (Child Referencing) 使用者 Document 中包含一個 Array,裡面儲存信用卡資料的
_id。- 缺點:當信用卡被註銷(刪除)時,必須手動更新使用者 Document 中的 Array 移除對應的 ID,否則會出現無效參照。

策略 D:Many 透過 Referencing 參照 1 (Parent Referencing) 反過來,在信用卡 Document 中儲存使用者的
userId。當需要查詢某張卡的持有人時,再去 User 表查詢。這是關係型資料庫最常見的做法,在 MongoDB 中也非常安全,能有效避免 16MB 上限問題。
3. Many to Many 關係
- 設計:其實就是 1 to Many 雙向參照的組合。通常會使用 Referencing Array 的方式在兩端互相引用
_id。
結語:設計 Schema 時的自我提問
在著手設計 MongoDB Schema 時,不妨先問自己以下四個問題:
- 這兩個 Entity 經常需要同時被查詢呈現給使用者嗎?
- 它們之間的關係是 1:1、1:N 還是 N:M?
- Many 這一端的資料量預估有多少?使用 Embedding 會不會有突破 16MB 上限的風險?
- 如果選用了 Referencing,有規劃好連帶刪除(Cascade Deletion)的機制嗎?
以我個人的實務經驗來說,最常出問題的地方往往是連帶刪除(Cascade Deletion)。無論你選擇同步刪除還是非同步背景刪除,只要系統中新增了關聯的 Entity,就務必實作清理指針的邏輯。很多開發者常常忘記這一點,導致資料庫中累積了大量的「孤兒資料」,久而久之維護成本將會非常高。
