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

6 Rules of Thumb for MongoDB Schema Design: Part 1 javascript

做者 William Zola, Lead Technical Support Engineer at MongoDBhtml

「我有不少使用SQL的經驗, 但在對於 MongoDB 還只是一個初學者。我應該如何爲 一對多 的關係建模呢?」 這是我工做時從 MongoDB 用戶收到很常見問題。java

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

這個話題有不少能夠講的,我將其分紅三個部分。數據庫

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

第二個話題,會涉及複雜的 Shema 設計,包括反範式化和雙向引用。數據庫設計

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

許多初學者認爲 MongoDB 中構建 1對多關係模型的惟一方法是 在父文檔中嵌入一個子文檔數組,但這個是不對的。你能夠嵌入一個文檔,但不意味着你應該嵌入一個文檔。post

在設計 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 的初學者來說,裏面的知識點仍是很是有價值的。後面還有兩篇,會接着看完並翻譯發表。但願對看到的人也有所幫助。

相關文章
相關標籤/搜索