国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

MongoDB中的范式與反范式

littleGrow / 1580人閱讀

摘要:反范式化與范式化相反將每個(gè)文檔所需的數(shù)據(jù)都嵌入在文檔內(nèi)部。決定何時(shí)采用范式化何時(shí)采用反范式化時(shí)比較困難的。范式化能夠提高數(shù)據(jù)寫入速度,反范式化能夠提高數(shù)據(jù)讀取速度。

原文地址:http://pwhack.me/post/2014-06-25-1 轉(zhuǎn)載注明出處

本文摘錄自《MongoDB權(quán)威指南》第八章,可以徹底回答以下兩個(gè)問題:

http://segmentfault.com/q/1010000000364944

http://segmentfault.com/q/1010000000364944

數(shù)據(jù)表示的方式有很多種,其中最重要的問題之一就是在多大程度上對(duì)數(shù)據(jù)進(jìn)行范式化。范式化(normalization)是將數(shù)據(jù)分散到多個(gè)不同的集合,不同集合之間可以相互引用數(shù)據(jù)。雖然很多文檔可以引用某一塊數(shù)據(jù),但是這塊數(shù)據(jù)只存儲(chǔ)在一個(gè)集合中。所以,如果要修改這塊數(shù)據(jù),只需修改保存這塊數(shù)據(jù)的那一個(gè)文檔就行了。但是,MongoDB沒有提供連接(join)工具,所以在不同集合之間執(zhí)行連接查詢需要進(jìn)行多次查詢。

反范式化(denormalization)與范式化相反:將每個(gè)文檔所需的數(shù)據(jù)都嵌入在文檔內(nèi)部。每個(gè)文檔都擁有自己的數(shù)據(jù)副本,而不是所有文檔共同引用同一個(gè)數(shù)據(jù)副本。這意味著,如果信息發(fā)生了變化,那么所有相關(guān)文檔都需要進(jìn)行更新,但是在執(zhí)行查詢時(shí),只需要一次查詢,就可以得到所有數(shù)據(jù)。

決定何時(shí)采用范式化何時(shí)采用反范式化時(shí)比較困難的。范式化能夠提高數(shù)據(jù)寫入速度,反范式化能夠提高數(shù)據(jù)讀取速度。需要根據(jù)自己應(yīng)用程序的十幾需要仔細(xì)權(quán)衡。

數(shù)據(jù)表示的例子

假設(shè)要保存學(xué)生和課程信息。一種表示方式是使用一個(gè)students集合(每個(gè)學(xué)生是一個(gè)文檔)和一個(gè)classes集合(每門課程是一個(gè)文檔)。然后用第三個(gè)集合studentsClasses保存學(xué)生和課程之間的聯(lián)系。

> db.studentsClasses.findOne({"studentsId": id});
{
  "_id": ObjectId("..."),
  "studentId": ObjectId("...");
  "classes": [
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("...")
  ]
}

如果比較熟悉關(guān)系型數(shù)據(jù)庫(kù),可能你之前建國(guó)這種類型的表連接,雖然你的每個(gè)記過文檔中可能只有一個(gè)學(xué)生和一門課程(而不是一個(gè)課程“_id”列表)。將課程放在數(shù)組中,這有點(diǎn)兒MongoDB的風(fēng)格,不過實(shí)際上通常不會(huì)這么保存數(shù)據(jù),因?yàn)橐?jīng)歷很多次查詢才能得到真實(shí)信息。

假設(shè)要找到一個(gè)學(xué)生所選的課程。需要先查找students集合找到學(xué)生信息,然后查詢studentClasses找到課程“_id”,最后再查詢classes集合才能得到想要的信息。為了找出課程信息,需要向服務(wù)器請(qǐng)求三次查詢。很可能你并不想再M(fèi)ongoDB中用這種數(shù)據(jù)組織方式,除非學(xué)生信息和課程信息經(jīng)常發(fā)生變化,而且對(duì)數(shù)據(jù)讀取速度也沒有要求。

如果將課程引用嵌入在學(xué)生文檔中,就可以節(jié)省一次查詢:

{
  "_id": ObjectId("..."),
  "name": "John Doe",
  "classes": [
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("...")
  ]
}

"classes"字段是一個(gè)數(shù)組,其中保存了John Doe需要上的課程“_id”。需要找出這些課程的信息時(shí),就可以使用這些“_id”查詢classes集合。這個(gè)過程只需要兩次查詢。如果數(shù)據(jù)不需要隨時(shí)訪問也不會(huì)隨時(shí)發(fā)生變化(“隨時(shí)”比“經(jīng)常”要求更高),那么這種數(shù)據(jù)組織方式是非常好的。

如果需要進(jìn)一步優(yōu)化讀取速度,可以將數(shù)據(jù)完全反范式化,將課程信息作為內(nèi)嵌文檔保存到學(xué)生文檔的“classes”字段中,這樣只需要一次查詢就可以得到學(xué)生的課程信息了:

{
  "_id": ObjectId("..."),
  "name": "John Doe"
  "classes": [
    {
      "class": "Trigonometry",
      "credites": 3,
      "room": "204"
    },
    {
      "class": "Physics",
      "credites": 3,
      "room": "159"
    },
    {
      "class": "Women in Literature",
      "credites": 3,
      "room": "14b"
    },
    {
      "class": "AP European History",
      "credites": 4,
      "room": "321"
    }
  ]
}

上面這種方式的優(yōu)點(diǎn)是只需要一次查詢就可以得到學(xué)生的課程信息,缺點(diǎn)是會(huì)占用更多的存儲(chǔ)空間,而且數(shù)據(jù)同步更困難。例如,如果物理學(xué)的學(xué)分變成了4分(不再是3分),那么選修了物理學(xué)課程的每個(gè)學(xué)生文檔都需要更新,而且不只是更新“Physics”文檔。

最后,也可以混合使用內(nèi)嵌數(shù)據(jù)和引用數(shù)據(jù):創(chuàng)建一個(gè)子文檔數(shù)組用于保存常用信息,需要查詢更詳細(xì)信息時(shí)通過引用找到實(shí)際的文檔:

{
  "_id": ObjectId("..."),
  "name": "John Doe",
  "classes": [
    {
      "_id": ObjectId("..."),
      "class": "Trigonometry"    
    },
    {
      "_id": ObjectId("..."),
      "class": "Physics"
    }, {
      "_id": ObjectId("..."),
      "class": "Women in Literature"
    }, {
      "_id": ObjectId("..."),
      "class": "AP European History"
    }
  ]
}

這種方式也是不錯(cuò)的選擇,因?yàn)閮?nèi)嵌的信息可以隨著需求的變化進(jìn)行修改,如果希望在一個(gè)頁(yè)面中包含更多(或者更少)的信息,就可以將更多(或者更少)的信息放在內(nèi)嵌文檔中。

需要考慮的另一個(gè)重要問題是,信息更新更頻繁還是信息讀取更頻繁?如果這些數(shù)據(jù)會(huì)定期更新,那么范式化是比較好的選擇。如果數(shù)據(jù)變化不頻繁,為了優(yōu)化更新效率兒犧牲讀寫速度就不值得了。

例如,教科書上介紹范式化的一個(gè)例子可能是將用戶和用戶地址保存在不同的集合中。但是,人們幾乎不會(huì)改變住址,所以不應(yīng)該為了這種概率極小的情況(某人改變了住址)而犧牲每一次查詢的效率。在這種情況下,應(yīng)該將地址內(nèi)嵌在用戶文檔中。

如果決定使用內(nèi)嵌文檔,更新文檔時(shí),需要設(shè)置一個(gè)定時(shí)任務(wù)(cron job),以確保所做的每次更新都成功更新了所有文檔。例如,我們?cè)噲D將更新擴(kuò)散到多個(gè)文檔,在更新完成所有文檔之前,服務(wù)器崩潰了。需要能夠檢測(cè)到這種問題,并且重新進(jìn)行未完的更新。

一般來(lái)說,數(shù)據(jù)生成越頻繁,就越不應(yīng)該將這些內(nèi)嵌到其他文檔中。如果內(nèi)嵌字段或者內(nèi)嵌字段數(shù)量時(shí)無(wú)限增長(zhǎng)的,那么應(yīng)該將這些內(nèi)容保存在多帶帶的集合中,使用引用的方式進(jìn)行訪問,而不是內(nèi)嵌到其他文檔中,評(píng)論列表或者活動(dòng)列表等信息應(yīng)該保存在多帶帶的集合中,不應(yīng)該內(nèi)嵌到其他文檔中。

最后,如果某些字段是文檔數(shù)據(jù)的一部分,那么需要將這些字段內(nèi)嵌到文檔中。如果在查詢文檔時(shí)經(jīng)常需要將某個(gè)字段排除,那么這個(gè)字段應(yīng)該放在另外的集合中,而不是內(nèi)嵌在當(dāng)前的文檔中。

更適合內(nèi)嵌 更適合引用
子文檔較小 子文檔較大
數(shù)據(jù)不會(huì)定期改變 數(shù)據(jù)經(jīng)常改變
最終數(shù)據(jù)一致即可 中間階段的數(shù)據(jù)必須一致
文檔數(shù)據(jù)小幅增加 文檔數(shù)據(jù)大幅增加
數(shù)據(jù)通常需要執(zhí)行二次查詢才能獲得 數(shù)據(jù)通常不包含在結(jié)果中
快速讀取 快速寫入

假如我們有一個(gè)用戶集合。下面是一些可能需要的字段,以及它們是否應(yīng)該內(nèi)嵌到用戶文檔中。

用戶首選項(xiàng)(account preferences)

用戶首選項(xiàng)只與特定用戶相關(guān),而且很可能需要與用戶文檔內(nèi)的其他用戶信息一起查詢。所以用戶首選項(xiàng)應(yīng)該內(nèi)嵌到用戶文檔中。

最近活動(dòng)(recent activity)

這個(gè)字段取決于最近活動(dòng)增長(zhǎng)和變化的頻繁程度。如果這是個(gè)固定長(zhǎng)度的字段(比如最近的10次活動(dòng)),那么應(yīng)該將這個(gè)字段內(nèi)嵌到用戶文檔中。

好友(friends)

通常不應(yīng)該將好友信息內(nèi)嵌到用戶文檔中,至少不應(yīng)該將好友信息完全內(nèi)嵌到用戶文檔中。下節(jié)會(huì)介紹社交網(wǎng)絡(luò)應(yīng)用的相關(guān)內(nèi)容。

所有由用戶產(chǎn)生的內(nèi)容

不應(yīng)該內(nèi)嵌在用戶文檔中。

基數(shù)

一個(gè)集合中包含的對(duì)其他集合的引用數(shù)量叫做基數(shù)(cardinality)。常見的關(guān)系有一對(duì)一、一對(duì)多、多對(duì)多。假如有一個(gè)博客應(yīng)用程序。每篇博客文章(post)都有一個(gè)標(biāo)題(title),這是一個(gè)對(duì)一個(gè)的關(guān)系。每個(gè)作者(author)可以有多篇文章,這是一個(gè)對(duì)多的關(guān)系。每篇文章可以有多個(gè)標(biāo)簽(tag),每個(gè)標(biāo)簽可以在多篇文章中使用,所以這是一個(gè)多對(duì)多的關(guān)系。

在MongoDB中,many(多)可以被分拆為兩個(gè)子分類:many(多)和few(少)。假如,作者和文章之間可能是一對(duì)少的關(guān)系:每個(gè)作者只發(fā)表了為數(shù)不多的幾篇文章。博客文章和標(biāo)簽可能是多對(duì)少的關(guān)系:文章數(shù)量實(shí)際上很可能比標(biāo)簽數(shù)量多。博客文章和評(píng)論之間是一對(duì)多的關(guān)系:每篇文章可以擁有很多條評(píng)論。

只要確定了少與多的關(guān)系,就可以比較容易地在內(nèi)嵌數(shù)據(jù)和引用數(shù)據(jù)之間進(jìn)行權(quán)衡。通常來(lái)說,“少”的關(guān)系使用內(nèi)嵌的方式會(huì)比較好,“多”的關(guān)系使用引用的方式比較好。

好友、粉絲、以及其他的麻煩事情
  

親近朋友,遠(yuǎn)離敵人

很多社交類的應(yīng)用程序都需要鏈接人、內(nèi)容、粉絲、好友,以及其他一些事物。對(duì)于這些高度關(guān)聯(lián)的數(shù)據(jù)使用內(nèi)嵌的形式還是引用的形式不容易權(quán)衡。這一節(jié)會(huì)介紹社交圖譜數(shù)據(jù)相關(guān)的注意事項(xiàng)。通常,關(guān)注、好友或者收藏可以簡(jiǎn)化為一個(gè)發(fā)布、訂閱系統(tǒng):一個(gè)用戶可以訂閱另一個(gè)用戶相關(guān)的通知。這樣,有兩個(gè)基本操作需要比較高效:如何保存訂閱者,如何將一個(gè)事件通知給所有訂閱者。

比較常見的訂閱實(shí)現(xiàn)方式有三種。第一種方式是將內(nèi)容生產(chǎn)者內(nèi)嵌在訂閱者文檔中:

{
    "_id": ObjectId("..."),
    "username": "batman",
    "email": "batman@waynetech.com",
    "following": [
        ObjectId("..."),
        ObjectId("...")
    ]
}

現(xiàn)在,對(duì)于一個(gè)給定的用戶文檔,可以使用形如db.activities.find({"user": {"$in": user["following"]}})的方式查詢?cè)撚脩舾信d趣的所有活動(dòng)信息。但是,對(duì)于一條剛剛發(fā)布的活動(dòng)信息,如果要找出對(duì)這條信息感興趣的所有用戶,就不得不查詢所有用戶的“following”字段了。

另一種方式是將訂閱者內(nèi)嵌到生產(chǎn)者文檔中:

{
    "_id": ObjectId("..."),
    "username": "joker",
    "email": "joker@mailinator.com",
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("...")
    ]
}

當(dāng)這個(gè)生產(chǎn)者新發(fā)布一條信息時(shí),我們立即就可以知道需要給哪些用戶發(fā)布通知。這樣做的缺點(diǎn)時(shí),如果需要找到一個(gè)用戶關(guān)注的用戶列表,就必須查詢整個(gè)用戶集合。這樣方式的優(yōu)缺點(diǎn)與第一種方式的優(yōu)缺點(diǎn)恰好相反。

同時(shí),這兩種方式都存在另一個(gè)問題:它們會(huì)使用戶文檔變得越來(lái)越大,改變也越來(lái)越頻繁。通常,“following”和“followers”字段甚至不需要返回:查詢粉絲列表有多頻繁?如果用戶比較頻繁地關(guān)注某些人或者對(duì)一些人取消關(guān)注,也會(huì)導(dǎo)致大量的碎片。因此,最后的方案對(duì)數(shù)據(jù)進(jìn)一步范式化,將訂閱信息保存在多帶帶的集合中,以避免這些缺點(diǎn)。進(jìn)行這種成都的范式化可能有點(diǎn)兒過了,但是對(duì)于經(jīng)常發(fā)生變化而且不需要與文檔其他字段一起返回的字段,這非常有用。對(duì)“followers”字段做這種范式化使有意義的。

用一個(gè)集合來(lái)保存發(fā)布者和訂閱者的關(guān)系,其中的文檔結(jié)構(gòu)可能如下所示:

{
    "_id": ObjectId("..."),   //被關(guān)注者的"_id"
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("...")
    ]
}

這樣可以使用戶文檔比較精簡(jiǎn),但是需要額外的查詢才能得到粉絲列表。由于“followers”數(shù)組的大小經(jīng)常會(huì)發(fā)生變化,所以可以在這個(gè)集合上啟用“usePowerOf2Sizes”,以保證users集合盡可能小。如果將followers集合保存在另一個(gè)數(shù)據(jù)庫(kù)中,也可以在不過多影響users集合的前提下對(duì)其進(jìn)行壓縮。

應(yīng)對(duì)威爾惠頓效應(yīng)

不管使用什么樣的策略,內(nèi)嵌字段只能在子文檔或者引用數(shù)量不是特別大的情況下有效發(fā)揮作用。對(duì)于比較有名的用戶,可能會(huì)導(dǎo)致用于保存粉絲列表的文檔溢出。對(duì)于這種情況的一種解決方案使在必要時(shí)使用“連續(xù)的”文檔。例如:

> db.users.find({"username": "wil"})
{
    "_id": ObjectId("..."),
    "username": "wil",
    "email": "wil@example.com",
    "tbc": [
        ObjectId("123"),    // just for example
        ObjectId("456")     // same as above
    ],
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}
{
    "_id": ObjectId("123"),
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}
{
    "_id": ObjectId("456"),
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}

對(duì)于這種情況,需要在應(yīng)用程序中添加從“tbc”(to be continued)數(shù)組中取數(shù)據(jù)的相關(guān)邏輯。

說點(diǎn)什么

No silver bullet.

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/18705.html

相關(guān)文章

  • 數(shù)據(jù)庫(kù)面試題

    摘要:不過這里的時(shí)間指的是系統(tǒng)版本號(hào)死鎖數(shù)據(jù)庫(kù)的解釋現(xiàn)象兩個(gè)或兩個(gè)以上事務(wù)在同一資源相互占用,并請(qǐng)求鎖定對(duì)方占用的資源,從而導(dǎo)致惡性循環(huán)的現(xiàn)象。并發(fā)控制解決問題我在讀數(shù)據(jù),你在刪數(shù)據(jù)的情況鎖分類讀鎖共享鎖,不阻塞寫鎖排他鎖,排除其他寫鎖和讀鎖。 數(shù)據(jù)庫(kù)面試題 showImg(https://s2.ax1x.com/2019/01/11/FXc580.md.jpg); DBS DBMS DB區(qū)...

    tabalt 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<