亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數據庫 > MongoDB > 正文

深入理解MongoDB的復合索引

2020-10-29 18:44:01
字體:
來源:轉載
供稿:網友

為什么需要索引?

當你抱怨MongoDB集合查詢效率低的時候,可能你就需要考慮使用索引了,為了方便后續介紹,先科普下MongoDB里的索引機制(同樣適用于其他的數據庫比如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 }

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

什么是復合索引?

復合索引,即Compound Index,指的是將多個鍵組合到一起創建索引,這樣可以加速匹配多個鍵的查詢。不妨通過一個簡單的示例理解復合索引。

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兩個鍵分別創建了索引(_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" }]

當進行多鍵查詢時,可以通過explian()分析執行情況(結果僅保留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即根據索引去查詢文檔,查詢的時候需要使用name進行過濾。

為name和age創建復合索引:

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" }]

有了復合索引之后,同一個查詢的執行方式就不同了:

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的復合索引;FETCH即根據索引去查詢文檔,不需要過濾。

這個示例的數據量太小,并不能看出什么問題。但是實際上,當數據量很大,IXSCAN返回的索引比較多時,FETCH時進行過濾將非常耗時。接下來將介紹一個真實的案例。

定位MongoDB性能問題

隨著接收的錯誤數據不斷增加,我們Fundebug已經累計處理3.5億錯誤事件,這給我們的服務不斷帶來性能方面的挑戰,尤其對于MongoDB集群來說。

對于生產數據庫,配置profile,可以記錄MongoDB的性能數據。執行以下命令,則所有超過1s的數據庫讀寫操作都會被記錄下來。

db.setProfilingLevel(1,1000)

查詢profile所記錄的數據,會發現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集合中有數億個文檔,因此count操作比較慢也不算太意外。根據profile數據,這個查詢耗時28.5s,時間長得有點離譜。另外,numYield高達1414,這應該就是操作如此之慢的直接原因。根據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.

這就意味著大量時間消耗在讀取硬盤上,且讀了非常多次。可以推測,應該是索引的問題導致的。

不妨使用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建立復合索引,因此IXSCAN階段采用的是projectId索引,其nReturned為28338; FETCH階段需要根據createAt進行過濾,其nReturned為20853,過濾掉了7485個文檔;另外,IXSCAN與FETCH階段的executionTimeMillisEstimate分別為30ms和27815ms,因此基本上所有時間都消耗在了FETCH階段,這應該是讀取硬盤導致的。

創建復合索引

沒有為projectId和createAt創建復合索引是個尷尬的錯誤,趕緊補救一下:

db.events.createIndex({projectId:1,createTime:-1},{background: true})

在生產環境構建索引這種事最好是晚上做,這個命令一共花了大概7個小時吧!background設為true,指的是不要阻塞數據庫的其他操作,保證數據庫的可用性。但是,這個命令會一直占用著終端,這時不能使用CTRL + C,否則會終止索引構建過程。

復合索引創建成果之后,前文的查詢就快了很多(僅保留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的復合索引,因此非??欤换?6ms,性能提升了將近600倍!?。Ρ仁褂脧秃纤饕昂蟮慕Y果,發現totalDocsExamined從28338降到了0,表示使用復合索引之后不再需要去查詢文檔,只需要掃描索引就好了,這樣就不需要去訪問磁盤了,自然快了很多。

參考

  • MongoDB 復合索引
  • MongoDB文檔:Compound Indexes

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧美国产视频| 亚洲图片欧美午夜| 久久久亚洲欧洲日产国码aⅴ| 欧美性jizz18性欧美| 91精品国产色综合久久不卡98口| 欧美日韩精品在线视频| 欧美黑人巨大精品一区二区| 成人国产精品日本在线| 国内揄拍国内精品| 国产一区二区三区精品久久久| 91人人爽人人爽人人精88v| 久久视频精品在线| 亚洲欧美日本精品| 在线成人激情视频| 黄色成人在线播放| 久久影视电视剧凤归四时歌| 久久av中文字幕| 国产在线视频2019最新视频| 欧美日韩亚洲精品一区二区三区| 91爱爱小视频k| 亚洲欧美另类在线观看| 亚洲成人国产精品| 久久久久久一区二区三区| 一区二区三区在线播放欧美| 日韩电影中文 亚洲精品乱码| 在线观看日韩专区| 久久九九亚洲综合| 少妇高潮久久久久久潘金莲| 国产精品6699| 午夜精品一区二区三区av| 91久久久久久久久| 国产精品r级在线| 国产精品视频免费观看www| 成人免费淫片视频软件| 亚洲激情免费观看| 亚洲国产日韩欧美在线99| 久久av在线看| 日韩欧美在线中文字幕| 欧美亚洲一区在线| 日韩av在线网页| 中文字幕综合一区| 欧美肥臀大乳一区二区免费视频| 久久综合九色九九| 97在线看免费观看视频在线观看| 久久天堂av综合合色| 欧美成人亚洲成人| 2023亚洲男人天堂| 91在线观看免费网站| 日韩欧美综合在线视频| 国产成人aa精品一区在线播放| 欧美激情图片区| 亚洲美腿欧美激情另类| 91精品国产一区| 久久亚洲精品小早川怜子66| 精品在线小视频| 国产精品免费福利| 久久男人资源视频| 亚洲自拍中文字幕| 日韩av综合中文字幕| 日本欧美在线视频| 一本色道久久88综合亚洲精品ⅰ| 91日韩在线视频| 久久99久久99精品免观看粉嫩| 欧美日韩久久久久| 日韩精品视频中文在线观看| 亚洲免费视频观看| 97香蕉超级碰碰久久免费软件| 最近免费中文字幕视频2019| 欧美日韩色婷婷| 92裸体在线视频网站| 日韩中文第一页| 久久天天躁夜夜躁狠狠躁2022| 亚洲精品福利视频| 国内偷自视频区视频综合| 色悠久久久久综合先锋影音下载| 国产精品观看在线亚洲人成网| 亚洲视频专区在线| 国模私拍一区二区三区| 日韩国产高清污视频在线观看| 亚洲a级在线观看| 国产精品日韩一区| 国产噜噜噜噜久久久久久久久| 国产一区欧美二区三区| 日韩av有码在线| 国产精品夜色7777狼人| 亚洲欧美三级在线| 欧美国产在线视频| 欧美性极品xxxx做受| 日韩黄色av网站| 亚洲网站在线观看| 欧美精品精品精品精品免费| 国内偷自视频区视频综合| 亚洲第一福利网| 91精品久久久久久久久久另类| 日韩在线免费视频| 欧美专区日韩视频| 久久福利视频导航| 97人人模人人爽人人喊中文字| 久久不射电影网| 日韩精品中文字幕久久臀| 精品国产电影一区| 成人激情视频网| 久久久久久欧美| 欧美高清视频免费观看| 精品美女永久免费视频| 欧美国产日韩视频| 亚洲精品欧美日韩专区| 精品国产福利视频| 国产精品男人的天堂| 激情懂色av一区av二区av| 欧美大成色www永久网站婷| 久久精品视频导航| 亚洲一区二区在线| 国产大片精品免费永久看nba| 欧美最顶级的aⅴ艳星| 久久精品99久久香蕉国产色戒| 久久精品免费电影| 欧美床上激情在线观看| 国产女人18毛片水18精品| 精品国内亚洲在观看18黄| 热99精品里视频精品| 九九九热精品免费视频观看网站| 亚洲无线码在线一区观看| 久久婷婷国产麻豆91天堂| 国产成+人+综合+亚洲欧洲| 国产亚洲日本欧美韩国| 国产精品国内视频| 91在线免费视频| 国产成人午夜视频网址| 亚洲毛片在线观看.| 91精品国产成人www| 1769国产精品| 日韩人体视频一二区| 国产精品美女午夜av| 日韩精品视频免费在线观看| 91在线免费网站| 欧洲成人免费视频| 久久精品影视伊人网| 久久久亚洲国产| 91av网站在线播放| 日本精品视频网站| 97色在线观看免费视频| 久久久人成影片一区二区三区| 日韩国产欧美精品一区二区三区| 欧美肥老太性生活视频| 日韩一区二区三区xxxx| 久久大大胆人体| 中国china体内裑精亚洲片| 亚洲中国色老太| 亚洲精品ady| 久久精品小视频| 亚洲色图第一页| 一本一本久久a久久精品综合小说| 韩国v欧美v日本v亚洲| 亚洲综合自拍一区| 国产精品一区二区三区在线播放| 国产精品免费在线免费| 亚洲性夜色噜噜噜7777| 日韩大陆毛片av| 国产精品嫩草影院一区二区| 国产中文字幕91| 亚洲午夜色婷婷在线| 91福利视频在线观看| 精品久久久视频|