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

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

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

2020-10-29 18:44:01
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

為什么需要索引?

當(dāng)你抱怨MongoDB集合查詢(xún)效率低的時(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)過(guò)底層的存儲(chǔ)引擎持久化后,會(huì)有一個(gè)位置信息,通過(guò)這個(gè)位置信息,就能從存儲(chǔ)引擎里讀出該文檔。比如mmapv1引擎里,位置信息是『文件id + 文件內(nèi)offset 』, 在wiredtiger存儲(chǔ)引擎(一個(gè)KV存儲(chǔ)引擎)里,位置信息是wiredtiger在存儲(chǔ)文檔時(shí)生成的一個(gè)key,通過(guò)這個(gè)key能訪問(wèn)到對(duì)應(yīng)的文檔;為方便介紹,統(tǒng)一用pos(position的縮寫(xiě))來(lái)代表位置信息。

什么是復(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ù)合索引,因此非常快,只花了46ms,性能提升了將近600倍!!!對(duì)比使用復(fù)合索引前后的結(jié)果,發(fā)現(xiàn)totalDocsExamined從28338降到了0,表示使用復(fù)合索引之后不再需要去查詢(xún)文檔,只需要掃描索引就好了,這樣就不需要去訪問(wèn)磁盤(pán)了,自然快了很多。

參考

  • MongoDB 復(fù)合索引
  • MongoDB文檔:Compound Indexes

總結(jié)

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

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 久久久入口 | 国产精品自在线拍 | 激情久久精品 | 久色视频 | 久久久久成人免费 | 二区三区四区视频 | 亚州精品天堂中文字幕 | 免费的毛片 | 粉嫩粉嫩一区二区三区在线播放 | 999精品国产| 国产午夜电影在线观看 | 成人免费网站在线观看视频 | 91精品国产日韩91久久久久久360 | 91精品国产91热久久久做人人 | 黄色午夜剧场 | 96视频在线免费观看 | 国产成人77亚洲精品www | 日本黄色大片免费 | 91懂色 | 色屁屁xxxxⅹ免费视频 | 精品成人国产在线观看男人呻吟 | 一级片999| 在线播放av网址 | 男女隐私免费视频 | 国产不卡av在线 | 欧美精品国产综合久久 | 97人人草| 成人国产精品久久久 | 久久久久久久久久美女 | 国产精品视频免费在线观看 | 91精品动漫在线观看 | 激情综合在线观看 | 男女生羞羞视频网站在线观看 | 91九色免费视频 | 爱看久久 | 久草视频国产在线 | 99视频有精品 | 欧美一区黄色 | 欧美一区在线观看视频 | 国产亚洲欧美视频 | 日本a大片 |