摘要:摘要對(duì)于的多鍵查詢(xún),創(chuàng)建復(fù)合索引可以有效提高性能。不妨通過(guò)一個(gè)簡(jiǎn)單的示例理解復(fù)合索引。但是,使用的是與的復(fù)合索引即根據(jù)索引去查詢(xún)文檔,不需要過(guò)濾??梢酝茰y(cè),應(yīng)該是索引的問(wèn)題導(dǎo)致的。
摘要: 對(duì)于MongoDB的多鍵查詢(xún),創(chuàng)建復(fù)合索引可以有效提高性能。
什么是復(fù)合索引?復(fù)合索引,即Compound Index,指的是將多個(gè)鍵組合到一起創(chuàng)建索引,這樣可以加速匹配多個(gè)鍵的查詢(xún)。不妨通過(guò)一個(gè)簡(jiǎn)單的示例理解復(fù)合索引。
students集合如下:
db.students.find().pretty() { "_id" : ObjectId("5aa7390ca5be7272a99b042a"), "name" : "zhang", "age" : "15" } { "_id" : ObjectId("5aa7393ba5be7272a99b042b"), "name" : "wang", "age" : "15" } { "_id" : ObjectId("5aa7393ba5be7272a99b042c"), "name" : "zhang", "age" : "14" }
在name和age兩個(gè)鍵分別創(chuàng)建了索引(_id自帶索引):
db.students.getIndexes() [ { "v" : 1, "key" : { "name" : 1 }, "name" : "name_1", "ns" : "test.students" }, { "v" : 1, "key" : { "age" : 1 }, "name" : "age_1", "ns" : "test.students" } ]
當(dāng)進(jìn)行多鍵查詢(xún)時(shí),可以通過(guò)explian()分析執(zhí)行情況(結(jié)果僅保留winningPlan):
db.students.find({name:"zhang",age:"14"}).explain() "winningPlan": { "stage": "FETCH", "filter": { "name": { "$eq": "zhang" } }, "inputStage": { "stage": "IXSCAN", "keyPattern": { "age": 1 }, "indexName": "age_1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1, "direction": "forward", "indexBounds": { "age": [ "["14", "14"]" ] } } }
由winningPlan可知,這個(gè)查詢(xún)依次分為IXSCAN和FETCH兩個(gè)階段。IXSCAN即索引掃描,使用的是age索引;FETCH即根據(jù)索引去查詢(xún)文檔,查詢(xún)的時(shí)候需要使用name進(jìn)行過(guò)濾。
為name和age創(chuàng)建復(fù)合索引:
db.students.createIndex({name:1,age:1}) db.students.getIndexes() [ { "v" : 1, "key" : { "name" : 1, "age" : 1 }, "name" : "name_1_age_1", "ns" : "test.students" } ]
有了復(fù)合索引之后,同一個(gè)查詢(xún)的執(zhí)行方式就不同了:
db.students.find({name:"zhang",age:"14"}).explain() "winningPlan": { "stage": "FETCH", "inputStage": { "stage": "IXSCAN", "keyPattern": { "name": 1, "age": 1 }, "indexName": "name_1_age_1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1, "direction": "forward", "indexBounds": { "name": [ "["zhang", "zhang"]" ], "age": [ "["14", "14"]" ] } } }
由winningPlan可知,這個(gè)查詢(xún)的順序沒(méi)有變化,依次分為IXSCAN和FETCH兩個(gè)階段。但是,IXSCAN使用的是name與age的復(fù)合索引;FETCH即根據(jù)索引去查詢(xún)文檔,不需要過(guò)濾。
這個(gè)示例的數(shù)據(jù)量太小,并不能看出什么問(wèn)題。但是實(shí)際上,當(dāng)數(shù)據(jù)量很大,IXSCAN返回的索引比較多時(shí),F(xiàn)ETCH時(shí)進(jìn)行過(guò)濾將非常耗時(shí)。接下來(lái)將介紹一個(gè)真實(shí)的案例。
定位MongoDB性能問(wèn)題隨著接收的錯(cuò)誤數(shù)據(jù)不斷增加,我們Fundebug已經(jīng)累計(jì)處理3.5億錯(cuò)誤事件,這給我們的服務(wù)不斷帶來(lái)性能方面的挑戰(zhàn),尤其對(duì)于MongoDB集群來(lái)說(shuō)。
對(duì)于生產(chǎn)數(shù)據(jù)庫(kù),配置profile,可以記錄MongoDB的性能數(shù)據(jù)。執(zhí)行以下命令,則所有超過(guò)1s的數(shù)據(jù)庫(kù)讀寫(xiě)操作都會(huì)被記錄下來(lái)。
db.setProfilingLevel(1,1000)
查詢(xún)profile所記錄的數(shù)據(jù),會(huì)發(fā)現(xiàn)events集合的某個(gè)查詢(xún)非常慢:
db.system.profile.find().pretty() { "op" : "command", "ns" : "fundebug.events", "command" : { "count" : "events", "query" : { "createAt" : { "$lt" : ISODate("2018-02-05T20:30:00.073Z") }, "projectId" : ObjectId("58211791ea2640000c7a3fe6") } }, "keyUpdates" : 0, "writeConflicts" : 0, "numYield" : 1414, "locks" : { "Global" : { "acquireCount" : { "r" : NumberLong(2830) } }, "Database" : { "acquireCount" : { "r" : NumberLong(1415) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(1415) } } }, "responseLength" : 62, "protocol" : "op_query", "millis" : 28521, "execStats" : { }, "ts" : ISODate("2018-03-07T20:30:59.440Z"), "client" : "192.168.59.226", "allUsers" : [ ], "user" : "" }
events集合中有數(shù)億個(gè)文檔,因此count操作比較慢也不算太意外。根據(jù)profile數(shù)據(jù),這個(gè)查詢(xún)耗時(shí)28.5s,時(shí)間長(zhǎng)得有點(diǎn)離譜。另外,numYield高達(dá)1414,這應(yīng)該就是操作如此之慢的直接原因。根據(jù)MongoDB文檔,numYield的含義是這樣的:
The number of times the operation yielded to allow other operations to complete. Typically, operations yield when they need access to data that MongoDB has not yet fully read into memory. This allows other operations that have data in memory to complete while MongoDB reads in data for the yielding operation.
這就意味著大量時(shí)間消耗在讀取硬盤(pán)上,且讀了非常多次。可以推測(cè),應(yīng)該是索引的問(wèn)題導(dǎo)致的。
不妨使用explian()來(lái)分析一下這個(gè)查詢(xún)(僅保留executionStats):
db.events.explain("executionStats").count({"projectId" : ObjectId("58211791ea2640000c7a3fe6"),createAt:{"$lt" : ISODate("2018-02-05T20:30:00.073Z")}}) "executionStats": { "executionSuccess": true, "nReturned": 20853, "executionTimeMillis": 28055, "totalKeysExamined": 28338, "totalDocsExamined": 28338, "executionStages": { "stage": "FETCH", "filter": { "createAt": { "$lt": ISODate("2018-02-05T20:30:00.073Z") } }, "nReturned": 20853, "executionTimeMillisEstimate": 27815, "works": 28339, "advanced": 20853, "needTime": 7485, "needYield": 0, "saveState": 1387, "restoreState": 1387, "isEOF": 1, "invalidates": 0, "docsExamined": 28338, "alreadyHasObj": 0, "inputStage": { "stage": "IXSCAN", "nReturned": 28338, "executionTimeMillisEstimate": 30, "works": 28339, "advanced": 28338, "needTime": 0, "needYield": 0, "saveState": 1387, "restoreState": 1387, "isEOF": 1, "invalidates": 0, "keyPattern": { "projectId": 1 }, "indexName": "projectId_1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1, "direction": "forward", "indexBounds": { "projectId": [ "[ObjectId("58211791ea2640000c7a3fe6"), ObjectId("58211791ea2640000c7a3fe6")]" ] }, "keysExamined": 28338, "dupsTested": 0, "dupsDropped": 0, "seenInvalidated": 0 } } }
可知,events集合并沒(méi)有為projectId與createAt建立復(fù)合索引,因此IXSCAN階段采用的是projectId索引,其nReturned為28338; FETCH階段需要根據(jù)createAt進(jìn)行過(guò)濾,其nReturned為20853,過(guò)濾掉了7485個(gè)文檔;另外,IXSCAN與FETCH階段的executionTimeMillisEstimate分別為30ms和27815ms,因此基本上所有時(shí)間都消耗在了FETCH階段,這應(yīng)該是讀取硬盤(pán)導(dǎo)致的。
創(chuàng)建復(fù)合索引沒(méi)有為projectId和createAt創(chuàng)建復(fù)合索引是個(gè)尷尬的錯(cuò)誤,趕緊補(bǔ)救一下:
db.events.createIndex({projectId:1,createTime:-1},{background: true})
在生產(chǎn)環(huán)境構(gòu)建索引這種事最好是晚上做,這個(gè)命令一共花了大概7個(gè)小時(shí)吧!background設(shè)為true,指的是不要阻塞數(shù)據(jù)庫(kù)的其他操作,保證數(shù)據(jù)庫(kù)的可用性。但是,這個(gè)命令會(huì)一直占用著終端,這時(shí)不能使用CTRL + C,否則會(huì)終止索引構(gòu)建過(guò)程。
復(fù)合索引創(chuàng)建成果之后,前文的查詢(xún)就快了很多(僅保留executionStats):
db.javascriptevents.explain("executionStats").count({"projectId" : ObjectId("58211791ea2640000c7a3fe6"),createAt:{"$lt" : ISODate("2018-02-05T20:30:00.073Z")}}) "executionStats": { "executionSuccess": true, "nReturned": 0, "executionTimeMillis": 47, "totalKeysExamined": 20854, "totalDocsExamined": 0, "executionStages": { "stage": "COUNT", "nReturned": 0, "executionTimeMillisEstimate": 50, "works": 20854, "advanced": 0, "needTime": 20853, "needYield": 0, "saveState": 162, "restoreState": 162, "isEOF": 1, "invalidates": 0, "nCounted": 20853, "nSkipped": 0, "inputStage": { "stage": "COUNT_SCAN", "nReturned": 20853, "executionTimeMillisEstimate": 50, "works": 20854, "advanced": 20853, "needTime": 0, "needYield": 0, "saveState": 162, "restoreState": 162, "isEOF": 1, "invalidates": 0, "keysExamined": 20854, "keyPattern": { "projectId": 1, "createAt": -1 }, "indexName": "projectId_1_createTime_-1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1 } } }
可知,count操作使用了projectId和createAt的復(fù)合索引,因此非??欤换?strong>46ms,性能提升了將近600倍?。。?/strong>對(duì)比使用復(fù)合索引前后的結(jié)果,發(fā)現(xiàn)totalDocsExamined從28338降到了0,表示使用復(fù)合索引之后不再需要去查詢(xún)文檔,只需要掃描索引就好了,這樣就不需要去訪(fǎng)問(wèn)磁盤(pán)了,自然快了很多。
參考MongoDB 復(fù)合索引
MongoDB文檔:Compound Indexes
版權(quán)聲明:
轉(zhuǎn)載時(shí)請(qǐng)注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/15/mongdb_compound_index_detail/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/19222.html
摘要:全文索引在中每個(gè)集合只允許創(chuàng)建一個(gè)索引,因此不用擔(dān)心存在多個(gè)索引造成沖突的問(wèn)題。全文索引創(chuàng)建全文索引創(chuàng)建方法與創(chuàng)建單鍵索引復(fù)合索引類(lèi)似。 在MongoDB中通過(guò)建立索引可以進(jìn)行高效的查詢(xún),如果沒(méi)有索引MongoDB將會(huì)掃描整個(gè)集合與查詢(xún)的條件進(jìn)行匹配,這對(duì)于性能會(huì)造成很大的消耗。技術(shù)博客: Node.js技術(shù)棧 快速導(dǎo)航 Mongodb索引類(lèi)型 索引屬性 索引實(shí)例測(cè)試 索引(Ind...
摘要:操作符如何使用索引有一些查詢(xún)完全無(wú)法使用索引,也有一些查詢(xún)能夠比其他查詢(xún)更高效地使用索引。有時(shí)能夠使用索引,但是通常它并不知道要如何使用索引。索引對(duì)象和數(shù)組允許深入文檔內(nèi)部,對(duì)嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復(fù)合索引 簡(jiǎn)介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時(shí)不應(yīng)該使用索引 1、使用復(fù)合索引 在多...
摘要:操作符如何使用索引有一些查詢(xún)完全無(wú)法使用索引,也有一些查詢(xún)能夠比其他查詢(xún)更高效地使用索引。有時(shí)能夠使用索引,但是通常它并不知道要如何使用索引。索引對(duì)象和數(shù)組允許深入文檔內(nèi)部,對(duì)嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復(fù)合索引 簡(jiǎn)介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時(shí)不應(yīng)該使用索引 1、使用復(fù)合索引 在多...
摘要:所以,如果你很少對(duì)集合進(jìn)行讀取操作,建議不使用索引內(nèi)存使用由于索引是存儲(chǔ)在內(nèi)存中你應(yīng)該確保該索引的大小不超過(guò)內(nèi)存的限制。如果索引的大小大于內(nèi)存的限制,會(huì)刪除一些索引,這將導(dǎo)致性能下降。 本文圍繞索引、游標(biāo)兩部分進(jìn)行探索,對(duì)MongoDB數(shù)據(jù)庫(kù)的索引部分有一個(gè)大概的了解; 索引 索引通常能夠極大的提高查詢(xún)的效率,如果沒(méi)有索引,MongoDB在讀取數(shù)據(jù)時(shí)必須掃描集合中的每個(gè)文件并選取那些符...
摘要:但是需要手動(dòng)創(chuàng)建創(chuàng)建索引,索引可以重復(fù)創(chuàng)建,若創(chuàng)建已經(jīng)存在的索引,則會(huì)直接返回成功。單鍵索引值為一個(gè)單一的值,如字符串,數(shù)字或日期。多鍵索引值具有多個(gè)記錄,如數(shù)組。過(guò)期索引不能是復(fù)合索引。 索引的概念 索引是一種單獨(dú)的、物理的對(duì)數(shù)據(jù)庫(kù)表中一列或多列的值進(jìn)行排序的一種存儲(chǔ)結(jié)構(gòu),通過(guò)索引可以快速找到我們查詢(xún)的數(shù)據(jù)。提高查詢(xún)效率 mongodb索引種類(lèi) _id索引 單鍵索引 多鍵索引 復(fù)合...
閱讀 3754·2021-08-11 11:16
閱讀 1621·2019-08-30 15:44
閱讀 1995·2019-08-29 18:45
閱讀 2267·2019-08-26 18:18
閱讀 996·2019-08-26 13:37
閱讀 1565·2019-08-26 11:43
閱讀 2109·2019-08-26 11:34
閱讀 372·2019-08-26 10:59