日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

MongoDB組合索引的優(yōu)化

 命運(yùn)之輪 2016-07-11

mongodb.jpg

索引在查詢中占的地位無(wú)疑是重中之重,因此建立一個(gè)好的索引對(duì)查詢性能的影響也是立竿見(jiàn)影。來(lái)自10gen工程師A. Jesse Jiryu
Davis帶來(lái)的MongoDB上索引的優(yōu)化方法以及MongoDB索引的選擇機(jī)制,幫助大家縮小索引的選擇空間。

A. Jesse Jiryu Davis —— 10gen工程師,從事MongoDB、Python及Tornado。在Dzone上分享了MongoDB中組合索引的最佳建立方法以及索引中字段的最優(yōu)順序。并通過(guò)explain()輸出的結(jié)果來(lái)驗(yàn)證實(shí)際性能,同時(shí)還分析了MongoDB的查詢優(yōu)化器的索引選擇機(jī)制。

項(xiàng)目背景

預(yù)想中的項(xiàng)目是在MongoDB上建立一個(gè)類Disqus的評(píng)論系統(tǒng)(雖然Disqus使用的是Postgres,但是不影響我們討論)。這里儲(chǔ)存的評(píng)論可能是上萬(wàn)條,但是我們先從簡(jiǎn)單的4條談起。每條評(píng)論都擁有時(shí)間戳(timestamp)、匿名(發(fā)送)與否(anonymous)以及質(zhì)量評(píng)價(jià)(rating)這三個(gè)屬性:

{ timestamp: 1, anonymous: false, rating: 3 }  
{ timestamp: 2, anonymous: false, rating: 5 }  
{ timestamp: 3, anonymous:  true, rating: 1 }  
{ timestamp: 4, anonymous: false, rating: 2 } 

這里需要查詢的是anonymous = false而且timestamp在2 – 4之間的評(píng)論,查詢結(jié)果通過(guò)rating進(jìn)行排序。我們將分3步完成查詢的優(yōu)化并且通過(guò)MongoDB的explain()對(duì)索引進(jìn)行考量。

范圍查詢

首先從簡(jiǎn)單的查詢開(kāi)始 —— timestamps范圍在2-4的評(píng)論:

> db.comments.find( { timestamp: { $gte: 2, $lte: 4 } } ) 

查詢的結(jié)果很顯然是3條。然而這里的重點(diǎn)是通過(guò)explain()看MongoDB是如何去實(shí)現(xiàn)查詢的:

> db.comments.find( { timestamp: { $gte: 2, $lte: 4 } } ).explain()  
{  
    "cursor" : "BasicCursor",  
    "n" : 3,  
    "nscannedObjects" : 4,  
    "nscanned" : 4,  
    "scanAndOrder" : false 
    // ... snipped output ...  
} 

先看一下如何讀MongoDB的查詢計(jì)劃:首先看cursor的類型。“BasicCursor”可以稱得上一個(gè)警告標(biāo)志:它意味著MongoDB將對(duì)數(shù)據(jù)集做一個(gè)完全的掃描。當(dāng)數(shù)據(jù)集里包含上千萬(wàn)條信息時(shí),這完全是行不通的。所以這里需要在timestamp上加一個(gè)索引:

> db.comments.createIndex( { timestamp: 1 } ) 

現(xiàn)在再看explain()的輸出結(jié)果:

> db.comments.find( { timestamp: { $gte: 2, $lte: 4 } } ).explain()  
{  
    "cursor" : "BtreeCursor timestamp_1",  
    "n" : 3,  
    "nscannedObjects" : 3,  
    "nscanned" : 3,  
    "scanAndOrder" : false 
} 

現(xiàn)在cursor的類型明顯變成了“BtreeCuor timestamp_1”(timestamp_1為之前定義的索引名稱)。nscanned從4降到了3,因?yàn)檫@里Mongo使用了索引跳過(guò)了范圍外的文檔直接指向了需要查詢的文檔。

164_121109093616_1.jpg

對(duì)于定義了索引的查詢:nscanned體現(xiàn)了Mongo掃描字段索引的條數(shù),而nscannedObjects則為最終結(jié)果中查詢過(guò)的文檔數(shù)目。n則表示了返回文檔的數(shù)目。nscannedObjects至少包含了所有的返回文檔,即使Mongo明確了可以通過(guò)查看絕對(duì)匹配文件的索引。因此可以得出nscanned >= nscannedObjects >= n。對(duì)于簡(jiǎn)單查詢你可能期望3個(gè)數(shù)字是相等的。這意味著你做出了MongoDB使用的完美索引。

范圍查詢的基礎(chǔ)上添加等值查詢

然而什么情況下nscanned會(huì)大于n ?很顯然當(dāng)Mongo需要檢驗(yàn)一些指向不匹配查詢的文檔的字段索引。舉個(gè)例子,我需要過(guò)濾出anonymous = true的文檔:

> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).explain()  
{  
    "cursor" : "BtreeCursor timestamp_1",  
    "n" : 2,  
    "nscannedObjects" : 3,  
    "nscanned" : 3,  
    "scanAndOrder" : false 
} 

從explain()輸出結(jié)果上來(lái)看:雖然n從3降到了2,但是nscanned和nscannedObjects的值仍然為3。Mongo掃描了timestamp從2到4的索引,這就包含了anonymous = true/false的所有情況。在文件檢查完之前,更不會(huì)去濾掉下一個(gè)。

164_121109093837_1.jpg

那么如何才能回到完美的nscanned = nscannedObjects = n 上來(lái)?這里嘗試一個(gè)在timestamp和anonymous上的組合索引:

> db.comments.createIndex( { timestamp:1, anonymous:1 } )  
> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).explain()  
{  
    "cursor" : "BtreeCursor timestamp_1_anonymous_1",  
    "n" : 2,  
    "nscannedObjects" : 2,  
    "nscanned" : 3,  
    "scanAndOrder" : false 
} 

這次的情況好了一點(diǎn):nscannedObjects從3降到了2。但是nscanned仍然為3!Mongo還是做了timestamp 2到4上索引的全掃描。當(dāng)然當(dāng)檢查anonymous索引發(fā)現(xiàn)其值為true時(shí),Mongo選擇了直接跳過(guò)而沒(méi)有進(jìn)行文檔掃描。因此這也是為什么只有nscanned的值仍為2的原因。

164_121109100553_1.jpg

那么是否可以改善這個(gè)情況讓nscanned也降到2?你可能已經(jīng)注意到這點(diǎn)了:定義索引的次序存在問(wèn)題。是的,這里應(yīng)該是“anonymous,timestamp”而不是“timestamp,anonymous”:

> db.comments.createIndex( { anonymous:1, timestamp:1 } )  
> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).explain()  
{  
    "cursor" : "BtreeCursor anonymous_1_timestamp_1",  
    "n" : 2,  
    "nscannedObjects" : 2,  
    "nscanned" : 2,  
    "scanAndOrder" : false 
} 

對(duì)于MongoDB組合索引的關(guān)鍵字順序問(wèn)題和其他數(shù)據(jù)庫(kù)都是一樣的。假如使用anonymous作為索引的第一個(gè)關(guān)鍵字,Mongo則會(huì)直接調(diào)至anonymous = false文檔做timestamp 2到4的范圍掃描。

164_121109101255_1.jpg

這里結(jié)束了探索的第一部分,簡(jiǎn)單的了解了一下MongoDB組合索引的優(yōu)化思想。然而事實(shí)上這種情況只存在于理想之中。

不防設(shè)想一下索引中包含“anonymous”是否物有所值。打個(gè)比方:我們現(xiàn)在的系統(tǒng)擁上千萬(wàn)條的評(píng)論并且天查詢量也上千萬(wàn),那么縮減nscanned必將大幅度的提升系統(tǒng)的吞吐量。但是如果anonymous部分在索引中很少用到,那么顯而易見(jiàn)的可以把它從索引中剔除為經(jīng)常用到的字段節(jié)省空間。另一方面:雙字段索引肯定比單字段索引占更多的內(nèi)存,因此單字段的索引在內(nèi)存的開(kāi)銷上無(wú)疑也是更勝一籌。而在這里的情況就是:只有anounymous = true占很大比重的時(shí)候才會(huì)在全方面中得利。既然要全面考慮,那么我們還必須看一下MongoDB索引的選擇機(jī)制。

MongoDB的索引選擇機(jī)制

首先來(lái)看一個(gè)比較有趣的事情:在先前的例子中我們并沒(méi)有刪除索引,這樣的話在我們建立的3個(gè)索引中MongoDB總是會(huì)擇優(yōu)而取。為什么會(huì)出現(xiàn)這種情況?

MongoDB的優(yōu)化程序會(huì)在對(duì)比中選擇更優(yōu)秀的索引。首先,它會(huì)給查詢做一個(gè)初步的“最佳索引”;其次,假如這個(gè)最佳索引不存在它會(huì)做嘗試來(lái)選出表現(xiàn)最好的索引;最后優(yōu)化器還會(huì)記住所有類似查詢的選擇(只到大規(guī)模文件變動(dòng)或者索引上的變動(dòng))。

那么優(yōu)化器是如何定義查詢的“最佳索引”。最佳索引必須包含查詢中所有可以做過(guò)濾及需要排序的字段。此外任何用于范圍掃描的字段以及排序字段都必須排在做等值查詢的字段之后。如果存在不同的最佳索引,那么Mongo將隨機(jī)選擇。在這個(gè)例子中“anonymous,timestamp”明顯是最佳索引,所以很迅速的就做出了選擇。

鑒于這樣表述很蒼白,下面來(lái)詳細(xì)的看一下第二部分是如何工作的。當(dāng)優(yōu)化器需要在一堆沒(méi)有特別優(yōu)勢(shì)的索引中選擇一個(gè)時(shí),它會(huì)收集所有相關(guān)的索引進(jìn)行相關(guān)的查詢,并選出最先完成的索引。

舉個(gè)例子下面是個(gè)查詢語(yǔ)句:

db.comments.find({ timestamp: { $gte: 2, $lte: 4 }, anonymous: false }) 

全部的3個(gè)索引都是相關(guān)的,所以MongoDB將3條索引以任意的順序連接起來(lái)并標(biāo)注了每條索引依次進(jìn)入的入口:

164_121109094453_1.jpg

所有索性都返回了如下結(jié)果:

{ timestamp: 2, anonymous: false, rating: 5 } 

首先。在第二步,左邊和中間的索引都返回了:

{ timestamp: 3, anonymous:  true, rating: 1 } 

而右邊的索引明顯勝于其他的兩條索引:

{ timestamp: 4, anonymous: false, rating: 2 } 

在這個(gè)競(jìng)賽中,在右方的索引明顯比其他的兩個(gè)先完成查詢。那么在下一次比賽前,它會(huì)一直作為最佳索引存在。簡(jiǎn)而言之:存在多條索引的情況下,MongoDB首選nscanned值最低的索引。

等值、范圍查詢及排序

既然我們擁有了timestamps在2到4之間的完美索引,那么我們的最后一步是進(jìn)行排序。先從降序開(kāi)始:

> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).sort( { rating: -1 } ).explain()  
{  
    "cursor" : "BtreeCursor anonymous_1_timestamp_1",  
    "n" : 2,  
    "nscannedObjects" : 2,  
    "nscanned" : 2,  
    "scanAndOrder" : true 
} 

在之前通常都是這么做的,現(xiàn)在同樣很好:nscanned = nscannedObjects = n。但是千萬(wàn)別忽略這條:scanAndOrder = true。這就意味著MongoDB會(huì)把所有查詢出來(lái)的結(jié)果放進(jìn)內(nèi)存,然后進(jìn)行排序,接著一次性輸出結(jié)果。然而我們必須考慮:這將占用服務(wù)器大量的CPU和RAM。取代將結(jié)果分批次的輸出,Mongo把他們?nèi)糠胚M(jìn)內(nèi)存并一起輸出將大量爭(zhēng)用應(yīng)用程序服務(wù)器的資源。最終Mongo會(huì)強(qiáng)行給數(shù)據(jù)做一個(gè)32MB的限制,然后在內(nèi)存里給他們排序。雖然我們現(xiàn)在討論中只有4條評(píng)論,但是我們?cè)O(shè)計(jì)的是上千萬(wàn)條的系統(tǒng)!

那這里該如何處理scanAndOrder = true這個(gè)情況?我們需要加一個(gè)索引,讓Mongo可以直接轉(zhuǎn)到anonyous = false部分,并且要求的順序掃描這個(gè)部分:

> db.comments.createIndex( { anonymous: 1, rating: 1 } ) 

Mongo會(huì)使用這個(gè)索引嗎?當(dāng)然不會(huì),因?yàn)檫@條索引在比賽中贏不了擁有最小nscanned的索引。優(yōu)化器無(wú)法識(shí)別哪條索引會(huì)有益于排序。

所以需要使用hint來(lái)強(qiáng)制Mongo的選擇:

> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).sort( { rating: -1 }  
... ).hint( { anonymous: 1, rating: 1 } ).explain()  
{  
    "cursor" : "BtreeCursor anonymous_1_rating_1 reverse",  
    "n" : 2,  
    "nscannedObjects" : 3,  
    "nscanned" : 3,  
    "scanAndOrder" : false 
} 

語(yǔ)句hint中存在爭(zhēng)議和CreateIndex是差不多的。現(xiàn)在nscanned = 3但是scanAndOrder = false?,F(xiàn)在Mongo將反過(guò)來(lái)查詢“anonymous,rating”索引,獲得擁有正確順序的評(píng)論,然后再檢查每個(gè)文件的timestamp是否在范圍內(nèi)。

164_121109095058_1.jpg

這也是優(yōu)化器為什么不會(huì)選擇這條索引的而去執(zhí)行這個(gè)擁有低nscanned但是完全在內(nèi)存排序的舊“anonymous,timestamp”索引的原因。

我們以犧牲nscanned的代價(jià)解決了scanAndOrder = true的問(wèn)題;既然nscanned已不可減少,那么我們是否可以減少nscannedObjects?我們向索引中添加timestamp,這樣一來(lái)Mongo就不用去從每個(gè)文件中獲取了:

> db.comments.createIndex( { anonymous: 1, rating: 1, timestamp: 1 } ) 

同樣優(yōu)化器不會(huì)贊成這條索引我們必須hint它:

> db.comments.find(  
...     { timestamp: { $gte: 2, $lte: 4 }, anonymous: false }  
... ).sort( { rating: -1 }  
... ).hint( { anonymous: 1, rating: 1, timestamp: 1 } ).explain()  
{  
    "cursor" : "BtreeCursor anonymous_1_rating_1_timestamp_1 reverse",  
    "n" : 2,  
    "nscannedObjects" : 2,  
    "nscanned" : 3,  
    "scanAndOrder" : false,  
} 

終于盡善盡美了。Mongo遵循了類似之前的計(jì)劃,并且nscannedObjects也降到了2。

164_121109095344_1.jpg

當(dāng)然必須得考慮給索引加入timestamp是否是值得的,因?yàn)閠imestamp給內(nèi)存帶來(lái)的附加空間可能會(huì)讓你得不償失。

最終方案

最后綜合一下給出包含了等值測(cè)試、排序及范圍過(guò)濾查詢的索引建立方法:

  1. 等值測(cè)試

    在索引中加入所有需要做等值測(cè)試的字段,任意順序。

  2. 排序字段(多排序字段的升/降序問(wèn)題 )

    根據(jù)查詢的順序有序的向索引中添加字段。

  3. 范圍過(guò)濾

    以字段的基數(shù)(Collection中字段的不同值的數(shù)量)從低到高的向索引中添加范圍過(guò)濾字段。

當(dāng)然這里還有一個(gè)規(guī)則:如果索引中的等值或者范圍查詢字段不能過(guò)濾出Collection中90%以上的文檔,那么把它移除索引估計(jì)會(huì)更好一些。并且如果你在一個(gè)Collection上有多個(gè)索引,那么必須hint Mongos。

對(duì)于組合索引的建立,有很多的因素去決定。雖然本文不能讓你直接確定出一個(gè)最優(yōu)的索引,但是無(wú)疑可以讓你縮小索引建立時(shí)的選擇。

本文轉(zhuǎn)自:CSDN - 10gen工程師談MongoDB組合索引的優(yōu)化

原文鏈接:Optimizing MongoDB Compound Indexes

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多