1. 程式人生 > 程式設計 >【譯】MongoDB Shema 設計的6條經驗法則 1

【譯】MongoDB Shema 設計的6條經驗法則 1

6 Rules of Thumb for MongoDB Schema Design: Part 1

作者 William Zola, Lead Technical Support Engineer at MongoDB

“我有很多使用SQL的經驗, 但在對於 MongoDB 還只是一個初學者。我應該如何為 一對多 的關係建模呢?” 這是我工作時從 MongoDB 使用者收到很常見問題。

對於這個問題我沒辦法給出一個簡短的答案,因為方法有很多,不止某一種。MongoDB 有一個豐富而細微的語彙去表達在SQL中被扁平化為術語 “1 to N ” 的內容。

這個話題有很多可以講的,我將其分成三個部分。

第一個部分,我會談三個基礎的方法去建立 1對多 關係模型。

第二個話題,會涉及複雜的 Shema 設計,包括反正規化化和雙向引用。

最後一部分,我會回顧各種各樣的選擇,並提供一些在考慮構建一個 1對多 關係模型時的建議。

許多初學者認為 MongoDB 中構建 1對多關係模型的唯一方法是 在父檔案中嵌入一個子檔案陣列,但這個是不對的。你可以嵌入一個檔案,但不意味著你應該嵌入一個檔案。

在設計 MongoDB Shema 時, 你需要從一個你在使用 SQL 時從來沒想過的問題開始:這個關係的 基數 (cardinality,指 N 端的資料規模 ) 是怎麼樣的?簡言之:你需要更加細微地表述你的 1對多

關係:它是 1對少1對很多,還是 1 對非常多?根據它是哪種,你可以去選擇不同的方式對關係建模。

基礎知識:1對少 建模

一個人的地址可以作為 1對少 的示例。這個是一個關於內嵌子檔案的好案例 ——在 Person 物件中嵌入一組 address 檔案:


> db.person.findOne()
{
  name: 'Kate Monster',ssn: '123-456-7890',addresses : [
     { street: '123 Sesame St',city: 'Anytown',cc: 'USA' },{ street: '123 Avenue Q',city
: 'New York',cc: 'USA' } ] } 複製程式碼

這種設計具有內嵌子檔案的所有優缺點。主要的優點是 不必執行一個單獨查詢來獲取內嵌的資料,主要的缺點則是內嵌的資料沒辦法作為一個單獨的實體進行訪問。

例如,你在為一個任務追蹤系統建模,每一個 Person 都會有很多工分配到他們。在Person 檔案中的內嵌任務會讓 如顯示由於明天到期的任務 這樣的查詢變得比其實際需要的更加困難。對於這種案例,我會在下一個部分提供一個更合適的設計。

基礎知識:1對很多 建模

替換零部件訂購系統中產品的零部件可以作為 1對多 的示例。每一個產品可能都有幾百個替換李建,但絕不會超過幾千個左右(所有這些不同尺寸的螺栓,墊圈和墊圈加起來)。這是一個很好的參考案例,你可以將零件的 ObjectID 內嵌到產品的問題中。(這個示例中,我使用2位元組的 ObjectID,因為它們更易於閱讀:實際程式碼將使用12位元組的 ObjectID

每一個零部件 _( parts )_會有它們的自己的檔案:

> db.parts.findOne()
{
    _id : ObjectID('AAAA'),partno : '123-aff-456',name : '#4 grommet',qty: 94,cost: 0.94,price: 3.99
}
複製程式碼

每一個產品 _( products )_也會有它們自己的檔案,其檔案中還會所以包含組成產品的 partsObjectID 陣列:

> db.products.findOne()
{
    name : 'left-handed smoke shifter',manufacturer : 'Acme Corp',catalog_number: 1234,parts : [     // array of references to Part documents
        ObjectID('AAAA'),// reference to the #4 grommet above
        ObjectID('F17C'),// reference to a different Part
        ObjectID('D2AA'),// etc
    ]
}
複製程式碼

然後,您再使用應用程式級聯接來檢索特定產品的零件:

 // Fetch the Product document identified by this catalog number
> product = db.products.findOne({catalog_number: 1234});
   // Fetch all the Parts that are linked to this Product
> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;
複製程式碼

為了操作更加高效,你可能還需要為products.catalog_number 建立索引。注意parts._id一定有索引的,因此查詢 parts 會很快。

這種風格的引用能與內嵌檔案的優缺點起到互補的左右。每一個零件都是一個單獨的檔案,因此很容易對他們做獨立的查詢和更新。使用這個 Schema 也需要一點妥協,就是在獲取查詢零部件詳細資訊時需要在再做一次查詢。(但先保留這個問題,直到我們進入 part 2 - 反正規化化)。

作為一個額外的好處,這種 Schema 允許你在多個產品中使用不同的零件,所以你的 1對多 Schema 就變成了多對多的 Schema 不需要任何關聯表了。

基礎知識:1對非常多 建模

從不同機器上收集日誌資訊的事件日誌系統可以作為 1對很多 的示例。任何給定 host 可以生產超過16Mb 的檔案大小,即使你的的陣列中村咋都是 ObjectID 。這是一個 “父引用”經典案例 — 你可有一個host 檔案,並且在每一個日誌資訊檔案中儲存這個 Host 的 ObjectID 。

> db.hosts.findOne()
{
    _id : ObjectID('AAAB'),name : 'goofy.example.com',ipaddr : '127.66.66.66'
}

>db.logmsg.findOne()
{
    time : ISODate("2014-03-28T09:42:41.382Z"),message : 'cpu is on fire!',host: ObjectID('AAAB')       // Reference to the Host document
}
複製程式碼

你可以用一個有一點點不同的應用級別的聯合查詢得到一個host最近5000條Messages:

  // find the parent ‘host’ document
> host = db.hosts.findOne({ipaddr : '127.66.66.66'});  // assumes unique index
   // find the most recent 5000 log message documents linked to that host
> last_5k_msg = db.logmsg.find({host: host._id}).sort({time : -1}).limit(5000).toArray()
複製程式碼

回顧

如此可見,即使在這個基礎的級別,設計 MongoDB Schema 時 比 設計一個對比的 關係性的 Schema要思考的更多。有以下兩個的因素需要考慮:

  1. 1對多 中"多"端的實體需要獨立存在嗎?

  2. 這個關係的基數是什麼樣的:1對少,1對很多,還是1對非常多?

基於這些因素,你可以選擇三個基本的 1對多 Schema 設計:

  • 如果基數是 1對少並且不需要從父物件的上下文之外訪問內嵌的物件,則直接內嵌N端;

  • 如果基數為一對多 或者 N 端物件因故需要獨立存在,則內嵌 N 端物件的引用陣列。

  • 如果基數為一對很多,則在 N 端物件中引用 1端物件。

下一次,我們將瞭解如何使用雙向關係和非規範化來增強這些基礎的 Schema 效能。

寫在後面:

最開始是在這篇文章 MongoDB資料庫設計中6條重要的經驗法則 裡看到的,但是因為排版不是很容易閱讀,因此我直接閱讀了原文,並自己試著從新翻譯排版, 部分術語的理解也參考了 MongoDB資料庫設計中6條重要的經驗法則

文章雖然已經比較久了,但對我才開始使用的 MongoDB 的初學者來講,裡面的知識點還是非常有價值的。後面還有兩篇,會接著看完並翻譯發表。希望對看到的人也有所幫助。