麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 數(shù)據(jù)庫(kù) > MongoDB > 正文

深入理解MongoDB的復(fù)合索引

2020-03-14 12:50:32
字體:
供稿:網(wǎng)友

為什么需要索引

當(dāng)你抱怨MongoDB集合查詢效率低的時(shí)候,可能你就需要考慮使用索引了,為了方便后續(xù)介紹,先科普下MongoDB里的索引機(jī)制(同樣適用于其他的數(shù)據(jù)庫(kù)比如mysql)。

mongo-9552:PRIMARY> db.person.find(){ "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }{ "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }{ "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }{ "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }{ "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }

當(dāng)你往某各個(gè)集合插入多個(gè)文檔后,每個(gè)文檔在經(jīng)過底層的存儲(chǔ)引擎持久化后,會(huì)有一個(gè)位置信息,通過這個(gè)位置信息,就能從存儲(chǔ)引擎里讀出該文檔。比如mmapv1引擎里,位置信息是『文件id + 文件內(nèi)offset 』, 在wiredtiger存儲(chǔ)引擎(一個(gè)KV存儲(chǔ)引擎)里,位置信息是wiredtiger在存儲(chǔ)文檔時(shí)生成的一個(gè)key,通過這個(gè)key能訪問到對(duì)應(yīng)的文檔;為方便介紹,統(tǒng)一用pos(position的縮寫)來代表位置信息。

什么是復(fù)合索引

復(fù)合索引,即Compound Index,指的是將多個(gè)鍵組合到一起創(chuàng)建索引,這樣可以加速匹配多個(gè)鍵的查詢。不妨通過一個(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)行多鍵查詢時(shí),可以通過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è)查詢依次分為IXSCAN和FETCH兩個(gè)階段。IXSCAN即索引掃描,使用的是age索引;FETCH即根據(jù)索引去查詢文檔,查詢的時(shí)候需要使用name進(jìn)行過濾。

為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è)查詢的執(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è)查詢的順序沒有變化,依次分為IXSCAN和FETCH兩個(gè)階段。但是,IXSCAN使用的是name與age的復(fù)合索引;FETCH即根據(jù)索引去查詢文檔,不需要過濾。

這個(gè)示例的數(shù)據(jù)量太小,并不能看出什么問題。但是實(shí)際上,當(dāng)數(shù)據(jù)量很大,IXSCAN返回的索引比較多時(shí),F(xiàn)ETCH時(shí)進(jìn)行過濾將非常耗時(shí)。接下來將介紹一個(gè)真實(shí)的案例。

定位MongoDB性能問題

隨著接收的錯(cuò)誤數(shù)據(jù)不斷增加,我們Fundebug已經(jīng)累計(jì)處理3.5億錯(cuò)誤事件,這給我們的服務(wù)不斷帶來性能方面的挑戰(zhàn),尤其對(duì)于MongoDB集群來說。

對(duì)于生產(chǎn)數(shù)據(jù)庫(kù),配置profile,可以記錄MongoDB的性能數(shù)據(jù)。執(zhí)行以下命令,則所有超過1s的數(shù)據(jù)庫(kù)讀寫操作都會(huì)被記錄下來。

db.setProfilingLevel(1,1000)

查詢profile所記錄的數(shù)據(jù),會(huì)發(fā)現(xiàn)events集合的某個(gè)查詢非常慢:

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è)查詢耗時(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í)間消耗在讀取硬盤上,且讀了非常多次。可以推測(cè),應(yīng)該是索引的問題導(dǎo)致的。

不妨使用explian()來分析一下這個(gè)查詢(僅保留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進(jìn)行過濾,其nReturned為20853,過濾掉了7485個(gè)文檔;另外,IXSCAN與FETCH階段的executionTimeMillisEstimate分別為30ms和27815ms,因此基本上所有時(shí)間都消耗在了FETCH階段,這應(yīng)該是讀取硬盤導(dǎo)致的。

創(chuàng)建復(fù)合索引

沒有為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)建過程。

復(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  } }}

可知,count操作使用了projectId和createAt的復(fù)合索引,因此非常快,只花了46ms,性能提升了將近600倍!!!對(duì)比使用復(fù)合索引前后的結(jié)果,發(fā)現(xiàn)totalDocsExamined從28338降到了0,表示使用復(fù)合索引之后不再需要去查詢文檔,只需要掃描索引就好了,這樣就不需要去訪問磁盤了,自然快了很多。

參考

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到MongoDB頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 亚洲国产高清自拍 | 精品国产一区二区亚洲人成毛片 | 婷婷一区二区三区四区 | 久久撸视频| 精品视频一区二区三区四区 | 欧美视频一区二区三区在线观看 | 狠狠干91 | www.91sao| 色综合欧美 | 亚洲最新色 | 毛片在哪看| 一本视频在线观看 | 久久性生活免费视频 | 精品成人免费一区二区在线播放 | 欧美爱爱视频网站 | 香蕉成人在线视频 | 九九热精品在线视频 | 国产91精品久久久久久 | 一级毛片在线观看免费 | 久久久免费| 国产精品二区高清在线 | 国产精品久久久久久久久岛 | 手机免费看一级片 | 欧美成人午夜 | 日本一区二区久久 | 成人毛片免费看 | 久久精品成人影院 | 91香蕉影视| 在线成人亚洲 | 久久精品免费网站 | 欧美第1页 | av电影手机在线看 | 一级毛片免费一级 | 欧美日韩亚洲不卡 | a视频在线免费观看 | 日本a级一区 | 国产精品久久久网站 | 欧美视屏一区二区 | 1314成人网 | 成人不卡免费视频 | 欧美一区二区三区免费不卡 |