本篇文章給大家分享的是有關(guān)MongoDB中如何使用復(fù)合索引,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
成都創(chuàng)新互聯(lián)公司主營鄂托克前網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都app軟件開發(fā),鄂托克前h5微信平臺小程序開發(fā)搭建,鄂托克前網(wǎng)站營銷推廣歡迎鄂托克前等地區(qū)企業(yè)咨詢什么是復(fù)合索引?
復(fù)合索引,即Compound Index,指的是將多個鍵組合到一起創(chuàng)建索引,這樣可以加速匹配多個鍵的查詢。不妨通過一個簡單的示例理解復(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兩個鍵分別創(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)進行多鍵查詢時,可以通過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可知,這個查詢依次分為IXSCAN和FETCH兩個階段。IXSCAN即索引掃描,使用的是age索引;FETCH即根據(jù)索引去查詢文檔,查詢的時候需要使用name進行過濾。
為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ù)合索引之后,同一個查詢的執(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可知,這個查詢的順序沒有變化,依次分為IXSCAN和FETCH兩個階段。但是,IXSCAN使用的是name與age的復(fù)合索引;FETCH即根據(jù)索引去查詢文檔,不需要過濾。
這個示例的數(shù)據(jù)量太小,并不能看出什么問題。但是實際上,當(dāng)數(shù)據(jù)量很大,IXSCAN返回的索引比較多時,F(xiàn)ETCH時進行過濾將非常耗時。接下來將介紹一個真實的案例。
定位MongoDB性能問題
隨著接收的錯誤數(shù)據(jù)不斷增加,我們Fundebug已經(jīng)累計處理3.5億錯誤事件,這給我們的服務(wù)不斷帶來性能方面的挑戰(zhàn),尤其對于MongoDB集群來說。
對于生產(chǎn)數(shù)據(jù)庫,配置profile,可以記錄MongoDB的性能數(shù)據(jù)。執(zhí)行以下命令,則所有超過1s的數(shù)據(jù)庫讀寫操作都會被記錄下來。
db.setProfilingLevel(1,1000)
查詢profile所記錄的數(shù)據(jù),會發(fā)現(xiàn)events集合的某個查詢非常慢:
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ù)億個文檔,因此count操作比較慢也不算太意外。根據(jù)profile數(shù)據(jù),這個查詢耗時28.5s,時間長得有點離譜。另外,numYield高達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.
這就意味著大量時間消耗在讀取硬盤上,且讀了非常多次??梢酝茰y,應(yīng)該是索引的問題導(dǎo)致的。
不妨使用explian()來分析一下這個查詢(僅保留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集合并沒有為projectId與createAt建立復(fù)合索引,因此IXSCAN階段采用的是projectId索引,其nReturned為28338; FETCH階段需要根據(jù)createAt進行過濾,其nReturned為20853,過濾掉了7485個文檔;另外,IXSCAN與FETCH階段的executionTimeMillisEstimate分別為30ms和27815ms,因此基本上所有時間都消耗在了FETCH階段,這應(yīng)該是讀取硬盤導(dǎo)致的。
創(chuàng)建復(fù)合索引
沒有為projectId和createAt創(chuàng)建復(fù)合索引是個尷尬的錯誤,趕緊補救一下:
db.events.createIndex({projectId:1,createTime:-1},{background: true})
在生產(chǎn)環(huán)境構(gòu)建索引這種事最好是晚上做,這個命令一共花了大概7個小時吧!background設(shè)為true,指的是不要阻塞數(shù)據(jù)庫的其他操作,保證數(shù)據(jù)庫的可用性。但是,這個命令會一直占用著終端,這時不能使用CTRL + C,否則會終止索引構(gòu)建過程。
復(fù)合索引創(chuàng)建成果之后,前文的查詢就快了很多(僅保留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 } } }
以上就是MongoDB中如何使用復(fù)合索引,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。