成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

MongoDB復合索引詳解

qieangel2013 / 2559人閱讀

摘要:摘要對于的多鍵查詢,創(chuàng)建復合索引可以有效提高性能。不妨通過一個簡單的示例理解復合索引。但是,使用的是與的復合索引即根據(jù)索引去查詢文檔,不需要過濾??梢酝茰y,應該是索引的問題導致的。

摘要: 對于MongoDB的多鍵查詢,創(chuàng)建復合索引可以有效提高性能。

什么是復合索引?

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

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

當進行多鍵查詢時,可以通過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可知,這個查詢依次分為IXSCANFETCH兩個階段。IXSCAN即索引掃描,使用的是age索引;FETCH即根據(jù)索引去查詢文檔,查詢的時候需要使用name進行過濾。

為name和age創(chuàng)建復合索引:

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

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

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

定位MongoDB性能問題

隨著接收的錯誤數(shù)據(jù)不斷增加,我們Fundebug已經(jīng)累計處理3.5億錯誤事件,這給我們的服務不斷帶來性能方面的挑戰(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,這應該就是操作如此之慢的直接原因。根據(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,應該是索引的問題導致的。

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

創(chuàng)建復合索引

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

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

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

參考

MongoDB 復合索引

MongoDB文檔:Compound Indexes

版權(quán)聲明:
轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/15/mongdb_compound_index_detail/

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/19222.html

相關(guān)文章

  • MongoDB索引與優(yōu)化詳解

    摘要:全文索引在中每個集合只允許創(chuàng)建一個索引,因此不用擔心存在多個索引造成沖突的問題。全文索引創(chuàng)建全文索引創(chuàng)建方法與創(chuàng)建單鍵索引復合索引類似。 在MongoDB中通過建立索引可以進行高效的查詢,如果沒有索引MongoDB將會掃描整個集合與查詢的條件進行匹配,這對于性能會造成很大的消耗。技術(shù)博客: Node.js技術(shù)棧 快速導航 Mongodb索引類型 索引屬性 索引實例測試 索引(Ind...

    oujie 評論0 收藏0
  • MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數(shù)組、索引基數(shù)

    摘要:操作符如何使用索引有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時能夠使用索引,但是通常它并不知道要如何使用索引。索引對象和數(shù)組允許深入文檔內(nèi)部,對嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復合索引 簡介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時不應該使用索引 1、使用復合索引 在多...

    saucxs 評論0 收藏0
  • MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數(shù)組、索引基數(shù)

    摘要:操作符如何使用索引有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時能夠使用索引,但是通常它并不知道要如何使用索引。索引對象和數(shù)組允許深入文檔內(nèi)部,對嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復合索引 簡介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時不應該使用索引 1、使用復合索引 在多...

    tomlingtm 評論0 收藏0
  • 練習 MongoDB 操作 —— 索引篇(二)

    摘要:所以,如果你很少對集合進行讀取操作,建議不使用索引內(nèi)存使用由于索引是存儲在內(nèi)存中你應該確保該索引的大小不超過內(nèi)存的限制。如果索引的大小大于內(nèi)存的限制,會刪除一些索引,這將導致性能下降。 本文圍繞索引、游標兩部分進行探索,對MongoDB數(shù)據(jù)庫的索引部分有一個大概的了解; 索引 索引通常能夠極大的提高查詢的效率,如果沒有索引,MongoDB在讀取數(shù)據(jù)時必須掃描集合中的每個文件并選取那些符...

    luqiuwen 評論0 收藏0
  • mongodb索引

    摘要:但是需要手動創(chuàng)建創(chuàng)建索引,索引可以重復創(chuàng)建,若創(chuàng)建已經(jīng)存在的索引,則會直接返回成功。單鍵索引值為一個單一的值,如字符串,數(shù)字或日期。多鍵索引值具有多個記錄,如數(shù)組。過期索引不能是復合索引。 索引的概念 索引是一種單獨的、物理的對數(shù)據(jù)庫表中一列或多列的值進行排序的一種存儲結(jié)構(gòu),通過索引可以快速找到我們查詢的數(shù)據(jù)。提高查詢效率 mongodb索引種類 _id索引 單鍵索引 多鍵索引 復合...

    FWHeart 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<