關於mongodb創建索引的一些經驗總結(轉)


查看語句執行計划:  explain() 在mongodb3+版本后輸出格式發生改變:

詳情參見:https://docs.mongodb.com/v3.0/reference/method/cursor.explain/

查看執行耗時:db.collection名稱.find().explain('executionStats')

 

一,索引介紹


mongodb具有兩類索引,分別為單鍵索引和復合索引。

1.單鍵索引是最簡單的一種索引,創建單鍵索引的開銷要比復合索引小很多。單鍵索引主要用於針對單值查詢的條件。

2.復合索引是將文檔中的幾個鍵聯合起來創建的一種索引,創建這種索引需要更多的空間與性能開銷。分別體現在:

1).在給大量數據創建復合索引時,會阻塞數據庫的查詢,更不用說修改和插入操作了;

2).插入一條數據時,要花費更多的時間來給復合索引加數據;

3).創建的復合索引所站得空間大小根據數據的類型以及鍵的數量而有所不同。比如,如果你用五個NumberInt的鍵創建的復合索引的空間大小,並不會比兩個NumberInt和一個String類型創建的復合索引占用更多的空間。索引在設計數據類型時,盡量將數據類型設置為NumberInt類型,以及盡量少使用string類型的數據做索引;

二,創建索引


創建索引的語句很簡單。

1.單鍵索引的創建:db.test.ensureIndex({name:1},{name:'index_name'})

2.復合索引的創建:db.test.ensureIndex({name:1,age:1,sex:1},{name:'index_nas'})

//查詢當前文檔的索引
db.CollectionName.getIndexes()

//查詢當前庫的所有索引

db.system.indexes.find()

三,索引優化


索引的優化是一個重頭戲,需要詳細的來解釋。我得測試數據插入了100萬條。字段分別為name,sex,type,time,id

1.我們來看一個簡單的查詢:db.test.find({name:'name_1'}) 相信大家對這個查詢已經很熟悉了,然后我們來看看這個語句的索引執行計划:

{
    "cursor" : "BasicCursor",   查詢語句所用到的索引,而BasicCursor代表沒有索引
    "isMultiKey" : false,     是否為復合索引
    "n" : 1,       查詢到的結果數
    "nscannedObjects" : 1000000,    掃描的文檔數量
    "nscanned" : 1000000,     掃面的索引數量
    "nscannedObjectsAllPlans" : 1000000,   //影響的所有的被掃描文檔的總數量
    "nscannedAllPlans" : 1000000,      //所有被掃描的索引的總數量
    "scanAndOrder" : false,  是否排序
    "indexOnly" : false,
    "nYields" : 2,
    "nChunkSkips" : 0,
    "millis" : 342,   花費的時間
    "indexBounds" : {
         
    },
    "server" : "node1:27017"
}

從這個執行計划中可以看出,該條查詢語句查詢一條數據需要掃描整個表,這肯定扯淡了嘛,那這時候就該給這個字段創建索引了,創建一個單鍵索引

db.test.ensureIndex({name:1},{name:'index_name'})

創建完索引之后,再來查看看這條查詢語句的執行計划:

{
    "cursor" : "BtreeCursor index_name",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 1,
    "nscannedAllPlans" : 1,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "name" : [
            [
                "name_1",
                "name_1"
            ]
        ]
    },
    "server" : "node1:27017"
}

簡直是逆天啊,nscanned和nscannedObjects居然從100萬下降到1條,也就是查詢數據時,只掃描了一條就已經找到,而且花費的時間是0秒,沒有創建索引時,居然是342毫秒,絕對索引威武啊。

2.這時候我想通過type和sex來組合查詢某一條件的數據: db.test.find({type:1,sex:0}) 看看這句的執行計划:

{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 1000000,
    "nscanned" : 1000000,
    "nscannedObjectsAllPlans" : 1000000,
    "nscannedAllPlans" : 1000000,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 529,
    "indexBounds" : {
         
    },
    "server" : "node1:27017"
}

從這個計划中可以看出,為了查找幾萬條數據,它也掃描了整個表,很顯然,該創建索引了:

db.test.ensureIndex({type:1,sex:1},{name:'index_ts'})

創建完索引之后,再來執行查詢語句,看看執行計划:

db.test.find({type:1,sex:0}).explain()
{
    "cursor" : "BtreeCursor index_ts",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 55555,
    "nscanned" : 55555,
    "nscannedObjectsAllPlans" : 55555,
    "nscannedAllPlans" : 55555,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 112,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                0,
                0
            ]
        ]
    },
    "server" : "node1:27017"
}

很顯然,絕對是一個最佳索引,因為n=nscannedObjects=nscanned了,而且查詢時間從529毫秒下降到112毫秒了,這也是一個質的飛躍,可以明顯的看到,它使用了剛剛創建的index_ts索引。

現在我又有一個需求了,我想通過時間再來排序,好的,我們執行查詢語句: db.test.find({type:1,sex:0}).sort({time:-1}) 我們來看看這個查詢語句的執行計划:

{
    "cursor" : "BtreeCursor index_ts",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 1000000,
    "nscanned" : 1000000,
    "nscannedObjectsAllPlans" : 1000000,
    "nscannedAllPlans" : 1000000,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 1,
    "nChunkSkips" : 0,
    "millis" : 695,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                0,
                0
            ]
        ]
    },
    "server" : "node1:27017"
}

看到沒,這個查詢語句跟上一個創建索引之后的查詢出來的結果相差還是很大的,scanAndOrder和millis,時間花費了將近700毫秒,而且在查詢完畢之后還要排序,這也太不近人情了,就加了一個排序操作,怎么會讓它從白天鵝變成丑小鴨了呢?啊,關鍵參數就是scanAndOrder,意思就是在內存中把結果排序了嘛,那好啊,既然你如此薄情,那我就建個復合索引來對抗: db.test.ensureIndex({type:1,sex:1,time:-1},{name:'index_tst'})

 

{
    "cursor" : "BtreeCursor index_tst",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 55555,
    "nscanned" : 55555,
    "nscannedObjectsAllPlans" : 55555,
    "nscannedAllPlans" : 55555,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 126,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                0,
                0
            ]
        ],
        "time" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ]
    },
    "server" : "node1:27017"
}

看到了嗎?各種參數又回到最佳狀態了。這時候可能有人會問了,為什么要把time放到索引的最后而不是其它位置呢?其實這在創建索引時是有要求的,即:

  1. 將等值索引放在最前面

  2. 盡量將排序字段放在范圍字段的前面

  3. $nin和$ne跟索引沒有關系

    接下來我們再給查詢語句加條件: db.test.find({type:1,sex:0,id:{$gt:1,$lt:500000}}) 執行計划如下:

{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 1000000,
    "nscanned" : 1000000,
    "nscannedObjectsAllPlans" : 1000000,
    "nscannedAllPlans" : 1000000,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 2,
    "nChunkSkips" : 0,
    "millis" : 553,
    "indexBounds" : {
         
    },
    "server" : "node1:27017"
}

可以看到,只返回兩萬多條數據,但是卻掃描了整個表,這肯定是很蛋疼的事情嘛,索引走起:

db.test.ensureIndex({type:1,sex:1,id:1},{name:'index_tis'})

{
    "cursor" : "BtreeCursor index_tis",
    "isMultiKey" : false,
    "n" : 55555,
    "nscannedObjects" : 55555,
    "nscanned" : 55555,
    "nscannedObjectsAllPlans" : 55555,
    "nscannedAllPlans" : 55555,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 1,
    "nChunkSkips" : 0,
    "millis" : 137,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                0,
                0
            ]
        ],
        "id" : [
            [
                1,
                1000000
            ]
        ]
    },
    "server" : "node1:27017"
}

很顯然,這是個非常不錯的組合索引,那為何不把id放在其它地方,偏偏放在最后面呢?因為在mongodb中,索引是從左到右執行的,因此顯然要從左到右一次過濾最大數量的數據顯然type和sex的組合過濾數據量要比id高更多,因為id的忙查率要遠高於這兩個組合。

接着再把按time排序加上,查詢:db.test.find({type:1,sex:1,id:{$gt:0,$lt:1000000}}).sort({time:-1}).explain()

{
    "cursor" : "BasicCursor",
    "isMultiKey" : false,
    "n" : 55556,
    "nscannedObjects" : 1000000,
    "nscanned" : 1000000,
    "nscannedObjectsAllPlans" : 1000000,
    "nscannedAllPlans" : 1000000,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 1,
    "nChunkSkips" : 0,
    "millis" : 725,
    "indexBounds" : {
         
    },
    "server" : "node1:27017"
}

可以看到,這個查詢語句也是極其慢的,而且還要再內存中排序,所以肯定要創建索引了:

db.test.ensureIndex({type:1,sex:1,id:1,time:-1},{name:'index_tist'}) 我們先這樣創建索引,看看執行計划:

{
    "cursor" : "BtreeCursor index_tist",
    "isMultiKey" : false,
    "n" : 55556,
    "nscannedObjects" : 55556,
    "nscanned" : 55556,
    "nscannedObjectsAllPlans" : 55657,
    "nscannedAllPlans" : 55657,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 404,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                1,
                1
            ]
        ],
        "id" : [
            [
                0,
                1000000
            ]
        ],
        "time" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ]
    },
    "server" : "node1:27017"
}

看到了沒有,雖然查詢時間縮短了,但是這個查詢結果還是會排序結果,好,我們再把索引改改:

db.test.ensureIndex({type:1,sex:1,time:-1,id:1},{name:'index_tist'})

{
    "cursor" : "BtreeCursor index_tist",
    "isMultiKey" : false,
    "n" : 55556,
    "nscannedObjects" : 55556,
    "nscanned" : 55556,
    "nscannedObjectsAllPlans" : 55657,
    "nscannedAllPlans" : 55657,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 168,
    "indexBounds" : {
        "type" : [
            [
                1,
                1
            ]
        ],
        "sex" : [
            [
                1,
                1
            ]
        ],
        "time" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ],
        "id" : [
            [
                0,
                1000000
            ]
        ]
    },
    "server" : "node1:27017"
}

再來看看,快到什么程度了,這個查詢的速度和參數條件已經比上一個索引的快了很多,那為什么會出現這種情況呢?為什么time在id的前后會有不同的表現?這是因為通過type和sex字段過濾完之后,已經在內存中有了數據,而這些數據下一步需要怎么辦?是先通過id來篩選,還是按照排序篩選呢?這里有一個知識點,在把id放在time前面時,程序首先會取復合id值,然后再把復合的數據排序,但是如果id放在排序的后面,那么程序將直接通過順序掃描索引樹的方式取出復合id范圍的數據。

四,總結


1.mongodb創建索引難點在於排序和范圍查詢的字段位置選擇

2.mongodb的復合索引的索引截取查詢是順序的,即如果(a:1,b:1,c:1},則可以是查詢{a:1},{a:1,b:1},{a:1,b:1,c:1}中得任何一種都會使用該索引,其它查詢情況將不會用到該索引;

3.盡量創建更少的索引以提高數據庫性能

4.以上的索引優化只是生產環境的一部分,具體情況可能還要看自己的業務來定

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM