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

資訊專欄INFORMATION COLUMN

50個(gè)關(guān)于MongoDB模型設(shè)計(jì)的技巧

sf190404 / 2155人閱讀

摘要:另一個(gè)時(shí)間點(diǎn)的一致性很重要,對(duì)于難以協(xié)調(diào)不一致的應(yīng)用程序。應(yīng)該將模式設(shè)計(jì)為按應(yīng)用程序單元進(jìn)行查詢。

#1:速度優(yōu)先使用嵌入數(shù)據(jù),完整性優(yōu)先使用引用數(shù)據(jù)

多個(gè)文檔使用的數(shù)據(jù)可以使用嵌入(非規(guī)范化)或引用(規(guī)范化)。非規(guī)范化并不一定比規(guī)范化更好,反之亦然:每種方式都有自己的權(quán)衡,你應(yīng)該選擇最適合你的應(yīng)用程序的方式。

非規(guī)范化可能導(dǎo)致數(shù)據(jù)不一致:假設(shè)您想要將圖1-1中的蘋果更改為梨。如果更新完第一個(gè)文檔中的值但應(yīng)用程序崩潰未能及時(shí)更新其他文檔,則數(shù)據(jù)庫針對(duì)同一個(gè)對(duì)象將會(huì)產(chǎn)生兩個(gè)不同的值。

圖1-1。規(guī)范化架構(gòu)。fruit的字段在food中定義,在meals中引用。

不一致的問題并不是很大,但“不大”的程度取決于你所存儲(chǔ)的內(nèi)容。對(duì)于許多應(yīng)用程序來說,短暫的時(shí)間不一致是可以允許的:如果有人更改了他的用戶名,那么舊帖子用他的舊用戶名顯示幾個(gè)小時(shí)可能并不重要。如果即使短暫的不一致的值也不允許,那么您應(yīng)該進(jìn)行規(guī)范化的方式來存儲(chǔ)。

但是,如果采用規(guī)范化,則在每次要查找的內(nèi)容時(shí),應(yīng)用程序都必須執(zhí)行額外的查詢fruit(圖1-2)。如果您的應(yīng)用程序無法承受這種性能損失,并且以后可以解決不一致,那么您應(yīng)該進(jìn)行非規(guī)范化。

圖1-2。非規(guī)范化架構(gòu)。fruit同時(shí)存儲(chǔ)在food和meals中

這是一種權(quán)衡:您不能同時(shí)擁有最快的性能又能保持實(shí)時(shí)一致性。您必須確定哪個(gè)對(duì)您的應(yīng)用程序更重要。

示例:購物車訂單

假設(shè)我們正在為購物車應(yīng)用程序設(shè)計(jì)架構(gòu)。我們的應(yīng)用程序在MongoDB中存儲(chǔ)訂單,訂單應(yīng)包含哪些信息?

規(guī)范化架構(gòu)

Product:

 {
     "_id" : productId,
     "name" : name,
     "price" : price,
     "desc" : description
 }

Order:

   {
    "_id" : orderId,
    "user" : userInfo,
    "items" : [
        productId1,
        productId2,
        productId3
     ]
   }

我們將每個(gè)商品的_id存儲(chǔ)在訂單中。然后,當(dāng)我們顯示訂單的內(nèi)容時(shí),我們查詢orders集合以獲得正確的訂單,然后查詢商品集合以獲得與我們的_ids 列表相關(guān)聯(lián)的商品。 無法在此模型中使用單個(gè)查詢獲取完整訂單信息

如果更新了有關(guān)商品的信息,則引用此商品的所有文檔都將“更改”,因?yàn)檫@些文檔僅指向最終文檔。

標(biāo)準(zhǔn)化為我們提供了較慢的讀取和保證所有訂單的一致性視圖; 多個(gè)文檔可以自動(dòng)更改(因?yàn)閷?shí)際上只有引用文檔在變化)。

非規(guī)范化架構(gòu)

非規(guī)范化架構(gòu)

Product(與之前相同):

{
  "_id" : productId,
  "name" : name,
  "price" : price,
  "desc" : description
}

Order:

{
?    "_id" : orderId,
?    "user" : userInfo,
?    "items" : [
?        {
?            "_id" : productId1,
?            "name" : name1,
?            "price" : price1
?        },
?        {
?            "_id" : productId2,
?            "name" : name2,
?            "price" : price2
?        },
?        {
?            "_id" : productId3,
?            "name" : name3,
?            "price" : price3
?        }
?    ]
}

我們將商品信息作為嵌入式文檔存儲(chǔ)在訂單中。然后,當(dāng)我們顯示訂單時(shí),我們只需要進(jìn)行一次查詢。

如果有關(guān)產(chǎn)品的信息已更新,并且我們希望將更改關(guān)聯(lián)到訂單,我們必須多帶帶更新每個(gè)購物車。

非規(guī)范化使我們?cè)谒杏唵沃械淖x取速度更快,但在處理所有訂單一致性上會(huì)不是很方便; 不能跨多個(gè)文檔自動(dòng)更改產(chǎn)品詳細(xì)信息。

所以,應(yīng)該如何決定是規(guī)范化還是非規(guī)范化?

決策因素

有三個(gè)主要因素需要考慮:

對(duì)于非常罕見的數(shù)據(jù)更改,您是否為每次讀取付出了額外的代價(jià)?您可能會(huì)每次讀取商品10,000次其詳細(xì)信息才會(huì)發(fā)生變化。您是否要為10,000次讀取中的每次讀取進(jìn)行額外的查詢,以使該寫入更快或保證一致?大多數(shù)應(yīng)用程序讀比寫更重要,所以先要了解您的比例是多少。

您想要引用的數(shù)據(jù)實(shí)際上經(jīng)常發(fā)生變化的頻率是多少?變化越小,非規(guī)范化的論證就越有效。在大多數(shù)場(chǎng)景下,幾乎不值得引用很少變化的數(shù)據(jù),如姓名,出生日期,股票代碼和地址。

一致性有多重要?如果一致性很重要,那么您應(yīng)該進(jìn)行規(guī)范化。例如,假設(shè)多個(gè)文檔需要自動(dòng)地看到更改。如果我們?cè)O(shè)計(jì)的交易應(yīng)用程序某些證券只能在某些時(shí)間進(jìn)行交易,我們希望在它們無法交易時(shí)立即“鎖定”它們。然后我們可以使用單個(gè)鎖定文檔作為相關(guān)證券模型的引用。但是,在應(yīng)用程序級(jí)別執(zhí)行此類操作可能會(huì)更好,因?yàn)閼?yīng)用程序需要知道何時(shí)鎖定和解鎖的規(guī)則。

另一個(gè)時(shí)間點(diǎn)的一致性很重要,對(duì)于難以協(xié)調(diào)不一致的應(yīng)用程序。例如在在訂單示例中,我們有嚴(yán)格的層次結(jié)構(gòu):訂單從商品中獲取信息,商品永遠(yuǎn)不會(huì)從訂單中獲取信息。如果有多個(gè)“源”文檔,則很難確定應(yīng)該選取哪個(gè)。

但是,在這種訂單管理中,一致性實(shí)際上可能是有害的。假設(shè)我們想以20%的折扣出售商品。我們不想更改現(xiàn)有訂單中的任何信息,我們只想更新商品說明。因此在這種情況下,我們實(shí)際上需要一個(gè)時(shí)間點(diǎn)的快照數(shù)據(jù)(參見技巧#5:嵌入“時(shí)間點(diǎn)”數(shù)據(jù))。

需要很快的讀取速度嗎?如果讀取需要盡可能快,則應(yīng)該進(jìn)行非規(guī)范化。實(shí)時(shí)應(yīng)用程序通常應(yīng)盡可能地進(jìn)行非規(guī)范化存儲(chǔ)。

對(duì)訂單模型進(jìn)行非規(guī)范化有一個(gè)很好的場(chǎng)景:信息不會(huì)發(fā)生太大變化,及時(shí)變化了我們也不希望訂單反映這些變化。歸一化并沒有給我們?nèi)魏翁貏e的優(yōu)勢(shì)。

在這種情況下,最好的選擇是對(duì)訂單模式進(jìn)行非規(guī)范化。

進(jìn)一步閱讀:

Your Coffee Shop Doesn’t Use Two-Phase Commit舉例說明了現(xiàn)實(shí)世界系統(tǒng)如何處理一致性以及它與數(shù)據(jù)庫設(shè)計(jì)的關(guān)系。

提示#2:如果您需要面向未來的數(shù)據(jù),請(qǐng)進(jìn)行標(biāo)準(zhǔn)化

規(guī)范化“面向未來”的數(shù)據(jù):您應(yīng)該能夠?qū)?biāo)準(zhǔn)化數(shù)據(jù)用于將來以不同方式查詢數(shù)據(jù)的不同應(yīng)用程序。

這假定您有一些數(shù)據(jù)集,應(yīng)用程序會(huì)有較多迭代,將需要使用多年。有這樣的數(shù)據(jù)集,但大多數(shù)人的數(shù)據(jù)不斷發(fā)展,舊數(shù)據(jù)要么被更新,要么被丟棄。大多數(shù)人希望他們的數(shù)據(jù)庫在他們現(xiàn)在正在進(jìn)行的查詢上盡可能快地執(zhí)行,如果他們將來更改這些查詢,他們將針對(duì)新查詢優(yōu)化他們的數(shù)據(jù)庫。

此外,如果應(yīng)用程序發(fā)展比較成功,其數(shù)據(jù)集通常會(huì)變得非常特定于應(yīng)用程序。這并不是說它不能用于更多的應(yīng)用程序; 通常你至少會(huì)想要對(duì)它進(jìn)行元分析。但這與“面向未來”不同,它能夠經(jīng)得起10年來人們想要運(yùn)行的任何疑問。

提示#3:嘗試在單個(gè)查詢中獲取數(shù)據(jù)
注意
在本節(jié)中,應(yīng)用程序單元用作某些應(yīng)用程序工作的通用術(shù)語。如果您有Web或移動(dòng)應(yīng)用程序,則可以將應(yīng)用程序單元視為對(duì)后端的請(qǐng)求。其他一些例子:

對(duì)于桌面應(yīng)用程序,這可能是用戶交互。

對(duì)于分析系統(tǒng),這可能是一個(gè)圖表加載。

它基本上是一個(gè)獨(dú)立的工作單元,您的應(yīng)用程序可能會(huì)涉及訪問數(shù)據(jù)庫。

應(yīng)該將MongoDB模式設(shè)計(jì)為按應(yīng)用程序單元進(jìn)行查詢。

示例:博客

如果我們正在設(shè)計(jì)博客應(yīng)用程序,請(qǐng)求博客文章可能是一個(gè)應(yīng)用程序單元。當(dāng)我們顯示帖子時(shí),我們想要內(nèi)容,標(biāo)簽,關(guān)于作者的一些信息(雖然可能不是她的整個(gè)個(gè)人資料),以及帖子的評(píng)論。因此,我們將所有這些信息嵌入到post文檔中,我們可以在一個(gè)查詢中獲取該視圖所需的所有內(nèi)容。

請(qǐng)記住,目標(biāo)是每頁一個(gè)查詢,而不是一個(gè)文檔:有時(shí)我們可能會(huì)返回多個(gè)文檔或部分文檔(而不是每個(gè)字段)。例如,主頁面可能包含來自posts集合的最新十個(gè)帖子,但只有他們的標(biāo)題,作者和摘要:

> db.posts.find({}, {"title" : 1, "author" : 1, "slug" : 1, "_id" : 0}).sort(
... {"date" : -1}).limit(10)

每個(gè)標(biāo)記可能有一個(gè)頁面,其中包含具有給定標(biāo)記的最后20個(gè)帖子的列表:

> db.posts.find({"tag" : someTag}, {"title" : 1, "author" : 1, 
... "slug" : 1, "_id" : 0}).sort({"date" : -1}).limit(20)

將有一個(gè)多帶帶的authro集合,其中包含每個(gè)作者的完整配置文件。作者頁面很簡單,它只是author集合中的文檔 :

> db.authors.findOne({"name" : authorName})

posts集合中的文檔可能包含作者文檔中出現(xiàn)的信息的子集:可能是作者的姓名和縮略圖個(gè)人資料圖片。

請(qǐng)注意,應(yīng)用程序單元不必與單個(gè)文檔對(duì)應(yīng),盡管在某些先前描述的情況中會(huì)發(fā)生這種情況(博客文章和作者的頁面都包含在單個(gè)文檔中)。但是,在很多情況下,應(yīng)用程序單元將是多個(gè)文檔,但可通過單個(gè)查詢?cè)L問。

示例:圖片墻

假設(shè)我們有一個(gè)圖片墻,用戶在新線程或現(xiàn)有線程中發(fā)布由圖像和一些文本組成的消息。然后,一個(gè)應(yīng)用程序單元正在線程上查看20條消息,因此我們將每個(gè)人的帖子作為posts集合中的多帶帶文檔。當(dāng)我們想要顯示頁面時(shí),我們將執(zhí)行查詢:

> db.posts.find({"threadId" : id}).sort({"date" : 1}).limit(20)

然后,當(dāng)我們想要獲取下一頁消息時(shí),我們將在該線程上查詢接下來的20條消息,然后查詢20之后的消息,等等:

> db.posts.find({"threadId" : id, "date" : {"$gt" : latestDateSeen}}).sort(
... {"date" : 1}).limit(20)

然后我們可以放置索引{threadId : 1, date : 1}以獲得這些查詢的良好性能。

注意

我們不使用skip(20),因?yàn)榉秶m合分頁。

隨著您的應(yīng)用程序變得更加復(fù)雜,用戶和管理員請(qǐng)求更多功能,您需要為每個(gè)應(yīng)用程序單元生成多個(gè)查詢。對(duì)于任何足夠復(fù)雜的應(yīng)用程序,您可能最終會(huì)為您的應(yīng)用程序的一個(gè)更荒謬的功能進(jìn)行多個(gè)查詢。

提示#4:嵌入依賴字段

在考慮是否嵌入或引用文檔時(shí),請(qǐng)問自己是否要多帶帶查詢此字段中的信息,或僅在較大文檔的框架中查詢。例如,您可能想要查詢標(biāo)記,但只想鏈接回帶有該標(biāo)記的帖子,而不是鏈接回自己的標(biāo)記。與評(píng)論類似,您可能會(huì)有最近評(píng)論的列表,但人們有興趣訪問發(fā)起評(píng)論的帖子(除非評(píng)論是您的應(yīng)用程序中的一等公民)。

如果您一直在使用關(guān)系數(shù)據(jù)庫并且正在將現(xiàn)有模式遷移到MongoDB,則連接表是嵌入的絕佳候選者。基本上是鍵和值的表(例如標(biāo)簽,權(quán)限或地址)幾乎總是在MongoDB中更好地嵌入。

最后,如果只有一個(gè)文檔關(guān)注某些信息,則將信息嵌入該文檔中。

提示#5:嵌入“時(shí)間點(diǎn)”數(shù)據(jù)

正如提示#1中的訂單示例中所提到的速度優(yōu)先使用嵌入數(shù)據(jù),完整性優(yōu)先使用引用數(shù)據(jù),如果產(chǎn)品(例如,銷售)或獲得新縮略圖,您實(shí)際上并不希望訂單中的信息發(fā)生變化。應(yīng)嵌入任何類型的信息,您希望在特定時(shí)間對(duì)數(shù)據(jù)進(jìn)行快照。

訂單文檔中的另一個(gè)示例:地址字段也屬于“時(shí)間點(diǎn)”類別的數(shù)據(jù)。如果他更新了他的個(gè)人資料,您不希望用戶的歷史訂單發(fā)生變化。

提示#6:不要嵌入具有未綁定增長的字段

由于MongoDB存儲(chǔ)數(shù)據(jù)的方式,不斷地將信息附加到數(shù)組的末尾是相當(dāng)?shù)托У摹T谡J褂闷陂g,您希望數(shù)組和對(duì)象的大小相當(dāng)穩(wěn)定。

因此,嵌入20個(gè)子文檔,或100或1,000,000是可以的,但事先要預(yù)防事情的發(fā)生。允許文檔在使用時(shí)增長很多最后查詢速度可能比你想要的要慢。

評(píng)論通常是一個(gè)特殊的情況,因應(yīng)用程序而異。對(duì)于大多數(shù)應(yīng)用程序,評(píng)論應(yīng)嵌入其父文檔中。但是,對(duì)于評(píng)論是其自己的實(shí)體或通常有數(shù)百個(gè)或更多的應(yīng)用程序,它們應(yīng)存儲(chǔ)為多帶帶的文檔。

作為另一個(gè)例子,假設(shè)我們僅為了評(píng)論而創(chuàng)建一個(gè)應(yīng)用程序。提示#3中的圖像板示例嘗試在單個(gè)查詢中獲取數(shù)據(jù)是這樣的; 主要內(nèi)容是評(píng)論。在這種情況下,我們希望評(píng)論是多帶帶的文檔。

提示#7:預(yù)先填充任何可能的內(nèi)容

如果您知道您的文檔可能需要某些字段,那么在您第一次插入文檔時(shí)填充它們比在您創(chuàng)建字段時(shí)更有效。例如,假設(shè)您要為站點(diǎn)分析創(chuàng)建應(yīng)用程序,以查看一天中每分鐘訪問不同頁面的用戶數(shù)量。我們將有一個(gè)頁面集合,其中每個(gè)文檔代表一個(gè)頁面的6小時(shí)片段。我們希望每分鐘和每小時(shí)存儲(chǔ)信息:

{
?    "_id" : pageId,
?    "start" : time,
?    "visits" : {
?        "minutes" : [
?            [num0, num1, ..., num59],
?            [num0, num1, ..., num59],
?            [num0, num1, ..., num59],
?            [num0, num1, ..., num59],
?            [num0, num1, ..., num59],
?            [num0, num1, ..., num59]
?        ],
?        "hours" : [num0, ..., num5] 
?    }
}

我們?cè)谶@里有一個(gè)較大的優(yōu)勢(shì):我們知道從現(xiàn)在到結(jié)束時(shí)這些文件會(huì)是什么樣子。現(xiàn)在將有一個(gè)開始時(shí)間在接下來的六個(gè)小時(shí)內(nèi)每分鐘都有一個(gè)條目。然后會(huì)有很多類似的文檔。

因此,我們可以有一個(gè)批處理作業(yè),可以在非繁忙時(shí)間插入這些“模板”文檔,也可以在一天中穩(wěn)定地插入。此腳本可以插入看起來像這樣的文檔,替換 someTime為下一個(gè)6小時(shí)間隔應(yīng)該是如下的內(nèi)容:

{
?    "_id" : pageId,
?    "start" : someTime,
?    "visits" : {
?        "minutes" : [
?            [0, 0, ..., 0],
?            [0, 0, ..., 0],
?            [0, 0, ..., 0],
?            [0, 0, ..., 0],
?            [0, 0, ..., 0],
?            [0, 0, ..., 0]
?        ],
?        "hours" : [0, 0, 0, 0, 0, 0]
?    }
}

現(xiàn)在,當(dāng)您增加或設(shè)置這些計(jì)數(shù)器時(shí),MongoDB不需要為它們找到空間。它只是更新您已經(jīng)輸入的值,這要快得多。

例如,在小時(shí)開始時(shí),您的程序可能會(huì)執(zhí)行以下操作:

> db.pages.update({"_id" : pageId, "start" : thisHour}, 
... {"$inc" : {"visits.0.0" : 3}})

這個(gè)想法可以擴(kuò)展到其他類型的數(shù)據(jù),甚至集合和數(shù)據(jù)庫本身。如果您每天使用新的集合,也可以提前創(chuàng)建它們。

提示#8:盡可能預(yù)先分配空間

這與提示#6密切相關(guān)不嵌入具有未綁定增長的字段和提示#7:預(yù)先填充任何可能的內(nèi)容。這是一種優(yōu)化,一旦您知道您的文檔通常會(huì)增長到一定的大小,但它們的起始尺寸較小。當(dāng)您最初插入文檔時(shí),添加一個(gè)包含文檔將(最終)大小的字符串的垃圾字段,然后立即取消設(shè)置該字段:

> collection.insert({"_id" : 123, /* other fields */, "garbage" : someLongString})
> collection.update({"_id" : 123}, {"$unset" : {"garbage" : 1}})

這樣,MongoDB最初會(huì)將文檔放在某個(gè)位置,以便為其提供足夠的增長空間(圖1-3)。

提示#9:將嵌入信息存儲(chǔ)在數(shù)組中以進(jìn)行匿名訪問

經(jīng)常出現(xiàn)的問題是是否將信息嵌入數(shù)組或子文檔中。當(dāng)你總是知道你將要查詢什么時(shí),應(yīng)該使用子文檔。如果您有可能無法確切知道要查詢的內(nèi)容,請(qǐng)使用數(shù)組。當(dāng)你知道關(guān)于你要查詢的元素的一些標(biāo)準(zhǔn)時(shí),通常應(yīng)該使用數(shù)組。

圖1-3。如果您存儲(chǔ)的文檔具有將來需要的空間量,則無需稍后移動(dòng)。

假設(shè)我們正在編寫一個(gè)玩家選擇各種物品的游戲。我們可能會(huì)將角色文檔建模為:

{
?    "_id" : "fred",
?    "items" : {
?        "slingshot" : {
?            "type" : "weapon",
?            "damage" : 23,
?            "ranged" : true
?        },
?        "jar" : {
?             "type" : "container",
?             "contains" : "fairy"
?        },
?        "sword" : {
?             "type" : "weapon",
?             "damage" : 50,
?             "ranged" : false
?        }
?     }
}

現(xiàn)在,假設(shè)我們想要找到damage大于20的所有武器。我們不能!子文檔不允許您進(jìn)入items并說“給我任何damage超過20的項(xiàng)目。”您只能詢問 具體項(xiàng)目:“ items.slingshot.damage大于20?items.sword.damage?“等等。

如果您希望能夠在不知道其標(biāo)識(shí)符的情況下訪問任何項(xiàng)目,則應(yīng)該安排架構(gòu)以將項(xiàng)目存儲(chǔ)在數(shù)組中:

{
?    "_id" : "fred",
?    "items" : [
?        {
?            "id" : "slingshot",
?            "type" : "weapon",
?            "damage" : 23,
?            "ranged" : true
?        },
?        {
?             "id" : "jar",
?             "type" : "container",
?             "contains" : "fairy"
?        },
?        {
?             "id" : "sword",
?             "type" : "weapon",
?             "damage" : 50,
?             "ranged" : false
?        }
?     ]
}

現(xiàn)在您可以使用簡單的查詢,例如{"items.damage" : {"$gt" : 20}}。如果您需要匹配(例如damageranged)的給定項(xiàng)目的多個(gè)條件,則可以使用$elemMatch

那么,什么時(shí)候應(yīng)該使用子文檔而不是數(shù)組?當(dāng)您知道并且始終知道您正在訪問的字段的名稱時(shí)。

例如,假設(shè)我們跟蹤玩家的能力:她的力量,智力,智慧,靈巧,體質(zhì)和魅力。我們將始終知道我們正在尋找哪種具體能力,因此我們可以將其存儲(chǔ)為:

{
?    "_id" : "fred",
?    "race" : "gnome",
?    "class" : "illusionist",
?    "abilities" : {
?        "str" : 20,
?        "int" : 12,
?        "wis" : 18,
?        "dex" : 24,
?        "con" : 23,
?        "cha" : 22
?    }
}

當(dāng)我們想要找到一個(gè)特定的技能,我們可以看一下abilities.str,或者abilities.con,或者別的什么東西。我們永遠(yuǎn)不會(huì)想要找到一個(gè)超過20的能力,因?yàn)槲覀兛倳?huì)知道我們?cè)趯ふ沂裁础?/p> 提示#10:設(shè)計(jì)文檔應(yīng)該是充分考慮的

MongoDB應(yīng)該是一個(gè)龐大而笨重的數(shù)據(jù)存儲(chǔ)。也就是說,它幾乎不進(jìn)行任何處理,只是存儲(chǔ)和檢索數(shù)據(jù)。您應(yīng)該尊重這一目標(biāo)并盡量避免強(qiáng)制MongoDB執(zhí)行可在客戶端上執(zhí)行的任何計(jì)算。即使是“微不足道的”任務(wù),例如尋找平均值或求和字段,通常也應(yīng)該推送給客戶端進(jìn)行。

如果要查詢必須計(jì)算且未在文檔中明確顯示的信息,您有兩種選擇:

導(dǎo)致嚴(yán)重的性能損失(迫使MongoDB使用JavaScript進(jìn)行計(jì)算,請(qǐng)參閱提示#11:首選$ -operators到JavaScript)

在文檔中明確顯示信息

通常,您應(yīng)該只在文檔中明確顯示信息。

假設(shè)你要查詢的文檔,其中 applesoranges的總和為30。也就是說,你的文檔看起來是這樣的:

{
?    "_id" : 123,
?    "apples" : 10,
?    "oranges" : 5
}

鑒于上述文檔,查詢總數(shù)將需要使用JavaScript,因此效率非常低。而是total在文檔中添加一個(gè)字段:

{
?    "_id" : 123,
?    "apples" : 10,
?    "oranges" : 5,
?    "total" : 15
}

然后總數(shù)可以在applesoranges`改變時(shí)同時(shí)改變:

> db.food.update(criteria, 
... {"$inc" : {"apples" : 10, "oranges" : -2, "total" : 8}})
> db.food.findOne()
{
    "_id" : 123,
    "apples" : 20,
    "oranges" : 3,
    "total" : 23
}

如果您不確定更新是否會(huì)改變?nèi)魏蝺?nèi)容,這將變得更加棘手。例如,假設(shè)您希望能夠查詢水果的數(shù)字類型,但您不知道您的更新是否會(huì)添加新類型。

因此,假設(shè)您的文檔看起來像這樣:

{
?    "_id" : 123,
?    "apples" : 20,
?    "oranges : 3,
?    "total" : 2
}  

現(xiàn)在,如果您執(zhí)行的更新可能會(huì)或可能不會(huì)創(chuàng)建新字段,您是否增加total?如果更新最終創(chuàng)建新字段,則應(yīng)更新總計(jì):

> db.food.update({"_id" : 123}, {"$inc" : {"banana" : 3, "total" : 1}})

相反,如果香蕉田已經(jīng)存在,我們不應(yīng)該增加總數(shù)。但是從客戶端來看,我們不知道它是否存在!

有兩種方法可以解決這個(gè)問題:快速,不一致的方式,以及緩慢,一致的方式。

快速的方法是選擇total添加或不添加1并使我們的應(yīng)用程序意識(shí)到它需要檢查客戶端的實(shí)際總數(shù)。我們可以進(jìn)行持續(xù)的批處理作業(yè),以糾正我們最終遇到的任何不一致。

如果我們的應(yīng)用程序可以立即花費(fèi)額外的時(shí)間,我們可以執(zhí)行findAndModify“鎖定”文檔(設(shè)置其他寫入將手動(dòng)檢查的“鎖定”字段),返回文檔,然后發(fā)出更新解鎖文檔并更新字段和total正確:

> var result = db.runCommand({"findAndModify" : "food", 
... "query" : {/* other criteria */, "locked" : false},
... "update" : {"$set" : {"locked" : true}}})
>
> if ("banana" in result.value) {
...   db.fruit.update(criteria, {"$set" : {"locked" : false}, 
...       "$inc" : {"banana" : 3}})
... } else {
...   // increment total if banana field doesn"t exist yet
...   db.fruit.update(criteria, {"$set" : {"locked" : false}, 
...       "$inc" : {"banana" : 3, "total" : 1}})
... } 

正確的選擇取決于您的應(yīng)用。

提示#11:首選$ -operators到JavaScript

某些操作無法使用$-operators 完成 。對(duì)于大多數(shù)應(yīng)用程序而言,使文檔自給自足可以最大限度地降低必須執(zhí)行的查詢的復(fù)雜性。但是,有時(shí)您將不得不查詢無法用$-operators 表達(dá)的內(nèi)容。在這種情況下,JavaScript可以解決您的問題:您可以使用$where子句在查詢中執(zhí)行任意JavaScript。

$where在查詢中使用,請(qǐng)編寫一個(gè)返回true 或返回的JavaScript函數(shù)false(無論該文檔是否匹配$where)。所以,假設(shè)我們只想返回值member[0].agemember[1].age等于的記錄。我們可以這樣做:

> db.members.find({"$where" : function() { 
... return this.member[0].age == this.member[1].age;
... }})

正如您可能想象的那樣,$where 為您的查詢提供相當(dāng)多的能力。但是,它也很慢。

在幕后

$where由于MongoDB在幕后所做的事情需要很長時(shí)間:當(dāng)您執(zhí)行普通(非$where)查詢時(shí),您的客戶端將該查詢轉(zhuǎn)換為BSON并將其發(fā)送到數(shù)據(jù)庫。MongoDB也將數(shù)據(jù)存儲(chǔ)在BSON中,因此它基本上可以將您的查詢直接與數(shù)據(jù)進(jìn)行比較。這非常快速有效。

現(xiàn)在假設(shè)您有一個(gè)$where 必須作為查詢的一部分執(zhí)行的子句。MongoDB必須為集合中的每個(gè)文檔創(chuàng)建一個(gè)JavaScript對(duì)象,解析文檔的BSON并將其所有字段添加到JavaScript對(duì)象中。然后它會(huì)執(zhí)行您針對(duì)文檔發(fā)送的JavaScript,然后再次將其全部刪除。這是非常耗費(fèi)時(shí)間和資源的。

獲得更好的表現(xiàn)

$where必要時(shí)是一個(gè)很好的能力,但應(yīng)盡可能避免。實(shí)際上,如果您注意到您的查詢需要大量的$wheres,那么這是一個(gè)很好的跡象,表明您應(yīng)該重新考慮您的架構(gòu)。

如果需要$where查詢,您可以通過最小化創(chuàng)建它的文檔數(shù)量來減少性能損失。嘗試提出可以在沒有$where的情況下檢查的其他標(biāo)準(zhǔn),并首先列出該標(biāo)準(zhǔn); 到查詢到達(dá)時(shí)“運(yùn)行中”的文檔越少,所需的時(shí)間$where就越少 。

例如,假設(shè)我們有$where上面給出的例子,并且我們意識(shí)到,當(dāng)我們檢查兩個(gè)成員的年齡時(shí),我們僅適用于至少具有聯(lián)合成員資格的成員,可能是家庭成員:

> db.members.find({"type" : {$in : ["joint", "family"]}, 
... "$where" : function() {
...     return this.member[0].age == this.member[1].age;
... }})

現(xiàn)在,所有單個(gè)成員資格文檔將在查詢到達(dá)時(shí)排除$where

提示#12:隨時(shí)計(jì)算聚合

只要有可能,隨著時(shí)間的推移計(jì)算聚合$inc。例如,在提示#7:預(yù)先填充任何可能的內(nèi)容,我們有一個(gè)分析應(yīng)用程序,其中包含按分鐘和小時(shí)分列的統(tǒng)計(jì)信息。我們可以在遞增分鐘數(shù)的同時(shí)遞增小時(shí)統(tǒng)計(jì)數(shù)據(jù)。

如果您的聚合需要更多調(diào)整(例如,查找一小時(shí)內(nèi)的平均查詢數(shù)),請(qǐng)將數(shù)據(jù)存儲(chǔ)在分鐘字段中,然后進(jìn)行持續(xù)的批處理,以計(jì)算最新分鐘的平均值。由于計(jì)算聚合所需的所有信息都存儲(chǔ)在一個(gè)文檔中,因此甚至可以將此處理傳遞給客戶端以獲取更新的(未聚合的)文檔。批處理作業(yè)已經(jīng)記錄了較舊的文檔。

提示#13:編寫代碼來處理數(shù)據(jù)完整性問題

鑒于MongoDB的無模式特性以及非規(guī)范化的優(yōu)勢(shì),您需要在應(yīng)用程序中保持?jǐn)?shù)據(jù)的一致性。

許多ODM都有各種方法來強(qiáng)制執(zhí)行一致的模式以達(dá)到各種嚴(yán)格程度。但是,還存在上面提到的一致性問題:由系統(tǒng)故障引起的數(shù)據(jù)不一致(提示#1:速度重復(fù)數(shù)據(jù),完整性參考數(shù)據(jù))和MongoDB更新的限制(提示#10:設(shè)計(jì)文檔是充分考慮的)。對(duì)于這些類型的不一致,您需要實(shí)際編寫一個(gè)用于檢查數(shù)據(jù)的腳本。

如果您按照本章中的提示操作,最終可能會(huì)有相當(dāng)多的cron作業(yè),具體取決于您的應(yīng)用程序。例如,您可能有:

一致性修復(fù)程序

檢查計(jì)算和重復(fù)數(shù)據(jù)以確保每個(gè)人都具有一致的值。

預(yù)填充器

創(chuàng)建將來需要的文檔。

聚合

保持內(nèi)聯(lián)聚合為最新。

其他有用的腳本(與本章不嚴(yán)格相關(guān))可能是:

架構(gòu)檢查器

確保當(dāng)前使用的文檔集都具有一組字段,可以自動(dòng)更正它們,也可以通知您不正確的字段。

備份工作

fsync,定期鎖定和轉(zhuǎn)儲(chǔ)數(shù)據(jù)庫。

在后臺(tái)運(yùn)行檢查和保護(hù)數(shù)據(jù)的作業(yè)會(huì)讓您更加輕松地使用它。

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

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

相關(guān)文章

  • 數(shù)據(jù)庫

    摘要:編輯大咖說閱讀字?jǐn)?shù)用時(shí)分鐘內(nèi)容摘要對(duì)于真正企業(yè)級(jí)應(yīng)用,需要分布式數(shù)據(jù)庫具備什么樣的能力相比等分布式數(shù)據(jù)庫,他們條最佳性能優(yōu)化性能優(yōu)化索引與優(yōu)化關(guān)于索引與優(yōu)化的基礎(chǔ)知識(shí)匯總。 mysql 數(shù)據(jù)庫開發(fā)常見問題及優(yōu)化 這篇文章從庫表設(shè)計(jì),慢 SQL 問題和誤操作、程序 bug 時(shí)怎么辦這三個(gè)問題展開。 一個(gè)小時(shí)學(xué)會(huì) MySQL 數(shù)據(jù)庫 看到了一篇適合新手的 MySQL 入門教程,希望對(duì)想學(xué) ...

    mengbo 評(píng)論0 收藏0
  • 數(shù)據(jù)庫

    摘要:編輯大咖說閱讀字?jǐn)?shù)用時(shí)分鐘內(nèi)容摘要對(duì)于真正企業(yè)級(jí)應(yīng)用,需要分布式數(shù)據(jù)庫具備什么樣的能力相比等分布式數(shù)據(jù)庫,他們條最佳性能優(yōu)化性能優(yōu)化索引與優(yōu)化關(guān)于索引與優(yōu)化的基礎(chǔ)知識(shí)匯總。 mysql 數(shù)據(jù)庫開發(fā)常見問題及優(yōu)化 這篇文章從庫表設(shè)計(jì),慢 SQL 問題和誤操作、程序 bug 時(shí)怎么辦這三個(gè)問題展開。 一個(gè)小時(shí)學(xué)會(huì) MySQL 數(shù)據(jù)庫 看到了一篇適合新手的 MySQL 入門教程,希望對(duì)想學(xué) ...

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

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

0條評(píng)論

sf190404

|高級(jí)講師

TA的文章

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