分佈式文檔存儲數據庫之MongoDB索引管理

  前文咱們聊到了MongoDB的簡介、安裝和對collection的CRUD操做,回顧請參考http://www.javashuo.com/article/p-ergzixcf-nu.html;今天咱們來聊下mongodb的索引;html

  一、爲何要有索引?索引的做用是幹嗎的?node

  咱們知道mongodb一般應用在一些web站點,數據量很是大的場景中;在大數據的場景中,對於咱們要查詢一個數據,mongodb是否可以快速的響應結果就變得尤其的重要;這也是索引存在的意義;索引就是用來幫助咱們在很大的數據集中快速查詢咱們想要的數據;一般咱們在mongodb中插入一條數據時,mongodb會自動給咱們添加一個_id的字段,這個字段是mongodb內部本身維護,一般狀況咱們都不會去管它;在關係型數據庫中,咱們能夠在單個字段上構建索引,也能夠在多個字段上構建索引,之因此要在多個字段構建索引是由於咱們的查詢條件極可能用到的字段不僅一個;因此咱們構建索引的原則是根據查詢條件來構建;好比咱們要查詢年齡大於30的用戶有哪些,咱們就能夠把索引構建在年齡這個字段上,構建在其餘字段上,對於咱們要查詢年齡大於30這個條件來說是沒有意義的;因此構建索引一般咱們會去了解用戶最常的查詢,在用戶最常查詢的字段上構建索引,這樣能夠有效提升用戶的查詢;對於mongodb也是同樣的,索引的存在就是爲了提升咱們的查詢;git

  二、爲何索引可以幫助咱們快速查找呢?web

  首先索引是按照咱們指定的字段來構建,構建索引就是把咱們指定的字段抽取出來,而後提早排好序(或者按照必定規律的方式排列好),而後保存爲另一個collection;用戶在查找數據時,mongodb首先會去找索引,看看用戶的條件是否和索引匹配,可以匹配,索引就能告訴用戶要查詢的數據在那個地方,這樣就很快的找到用戶查詢的數據;假如咱們構建的索引沒有匹配用戶的查詢,那麼用戶的查詢會以遍歷的方式去查找,這樣一來無形之中速度就變慢了(本來不加索引,直接遍歷,如今有索引,要先查索引,沒有命中,還要遍歷);因此構建索引,若是必定是數據量很大的狀況才構建,數據量小,構建索引不但不會幫助咱們快速的查找內容,反而會拖慢咱們的查詢速度;其次在很大的數據量上,若是索引構建的字段沒有被查詢命中,那麼我構建的索引就無心義;mongodb

  三、索引在必定程度上是要影響用戶寫的性能shell

  咱們在某個字段構建好索引之後,用戶在寫數據時,一般會額外多一次寫io;對於寫請求,在沒有索引的狀況,用戶只須要寫一次io,有了索引用戶每寫一條數據,都會對應有一次寫索引的io;這樣一來在必定程度上對用戶的寫性能會有影響;但一般咱們構建索引都是在讀多寫少的場景中使用;在寫請求不是特別多的場景其實多一次寫io,比起讀請求的壓力咱們是能夠接受的;更況且有些數據庫支持延遲寫索引,所謂延遲寫索引是指用戶在插入數據時,它不當即寫索引,而是等一段時間再寫,這樣一來就有效的下降寫索引對用戶的寫請求性能的影響;數據庫

  上圖主要描述了索引和文檔的關係,在索引裏的數據一般是咱們指定的字段,用特定的排列方式組織在一塊兒,在用戶查詢某個數據時,就可以很快的從索引中拿到對應文檔的位置,從而不用每一個文檔挨着遍歷;這也是索引可以幫助咱們快速查找的緣由吧;bash

  四、索引類型函數

  索引是有類型的,不一樣類型的索引在內部組織索引的方式各不相同,不一樣類型的索引給咱們查詢帶來的效果也不一樣;常見的索引類型有b+ tree(平衡樹索引),hash索引、空間索引、全文索引等等;在mongodb中索引也有不少類型,不一樣的是咱們上面說的索引類型,b+ tree,hash索引這些都是從索引內部組織結構來描述;在mongodb中的索引咱們是從索引構建的位置來描述;mongodb中的索引有單鍵索引、組合索引、多鍵索引、空間索引、文本索引和hash索引;所謂單鍵索引是指構建在一個字段上的索引;組合索引指構建在多個字段上的索引;多鍵索引指將索引構建在一個鍵的值是一個子文檔的字段上;咱們知道文檔和文檔是能夠嵌套的,這也意味着一個文檔內部能夠引用另外一個文檔,一個文檔中的某個鍵對應的值也能夠是另一個子文檔;咱們把這種索引構建在一個文檔中的某個鍵是一個子文檔的某個字段上的索引叫作多鍵索引,它和單鍵索引不是對應的;空間索引指基於位置查詢的索引,但一般這種索引只有用到特定的方法來查詢時,它纔會生效,好比使用基於空間位置的函數;文本索引指支持搜索整個文檔中的文本信息,一般這種索引咱們也叫全文索引;hash索引指把某個字段的值作hash計算後組織的索引;這種索引有個特色就是時間複雜度是o(1);無論數據有多少,在查找數據時所用到的時間都是同樣的;之因此時間複雜度是o(1),緣由是hash計算每個值都是惟一的;這種索引的查找方式有點相似鍵值查找,不一樣的是hash背後對應的是一個hash桶,先找到hash桶,而後查找到對應的hash值;hash索引和b+樹索引最大的區別是,b+樹索引能夠查詢一個範圍,由於樹狀索引一般是把數據組織成一個有序的結構,而hash索引不能,hash索引只能查找一個精確的值,不能查找一個範圍;由於hash索引背後對應的是一個hash值,每一個hash值可能都不在一個hash桶,因此咱們假如要查詢年齡大於30歲的用戶,用hash索引就不適合,由於30和31的hash值可能就不在一個hash桶上;性能

  五、在mongodb數據庫上建立索引

  準備數據

> use testdb
switched to db testdb
> for (i=1;i<=1000000;i++) db.peoples.insert({name:"people"+i,age:(i%120),classes:(i%20)})
WriteResult({ "nInserted" : 1 })
> db.peoples.find().count()
1000000
> db.peoples.find()
{ "_id" : ObjectId("5fa943987a7deafb9e543326"), "name" : "people1", "age" : 1, "classes" : 1 }
{ "_id" : ObjectId("5fa943987a7deafb9e543327"), "name" : "people2", "age" : 2, "classes" : 2 }
{ "_id" : ObjectId("5fa943987a7deafb9e543328"), "name" : "people3", "age" : 3, "classes" : 3 }
{ "_id" : ObjectId("5fa943987a7deafb9e543329"), "name" : "people4", "age" : 4, "classes" : 4 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332a"), "name" : "people5", "age" : 5, "classes" : 5 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332b"), "name" : "people6", "age" : 6, "classes" : 6 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332c"), "name" : "people7", "age" : 7, "classes" : 7 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332d"), "name" : "people8", "age" : 8, "classes" : 8 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332e"), "name" : "people9", "age" : 9, "classes" : 9 }
{ "_id" : ObjectId("5fa943987a7deafb9e54332f"), "name" : "people10", "age" : 10, "classes" : 10 }
{ "_id" : ObjectId("5fa943987a7deafb9e543330"), "name" : "people11", "age" : 11, "classes" : 11 }
{ "_id" : ObjectId("5fa943987a7deafb9e543331"), "name" : "people12", "age" : 12, "classes" : 12 }
{ "_id" : ObjectId("5fa943987a7deafb9e543332"), "name" : "people13", "age" : 13, "classes" : 13 }
{ "_id" : ObjectId("5fa943987a7deafb9e543333"), "name" : "people14", "age" : 14, "classes" : 14 }
{ "_id" : ObjectId("5fa943987a7deafb9e543334"), "name" : "people15", "age" : 15, "classes" : 15 }
{ "_id" : ObjectId("5fa943987a7deafb9e543335"), "name" : "people16", "age" : 16, "classes" : 16 }
{ "_id" : ObjectId("5fa943987a7deafb9e543336"), "name" : "people17", "age" : 17, "classes" : 17 }
{ "_id" : ObjectId("5fa943987a7deafb9e543337"), "name" : "people18", "age" : 18, "classes" : 18 }
{ "_id" : ObjectId("5fa943987a7deafb9e543338"), "name" : "people19", "age" : 19, "classes" : 19 }
{ "_id" : ObjectId("5fa943987a7deafb9e543339"), "name" : "people20", "age" : 20, "classes" : 0 }
Type "it" for more
> it
{ "_id" : ObjectId("5fa943987a7deafb9e54333a"), "name" : "people21", "age" : 21, "classes" : 1 }
{ "_id" : ObjectId("5fa943987a7deafb9e54333b"), "name" : "people22", "age" : 22, "classes" : 2 }
{ "_id" : ObjectId("5fa943987a7deafb9e54333c"), "name" : "people23", "age" : 23, "classes" : 3 }
{ "_id" : ObjectId("5fa943987a7deafb9e54333d"), "name" : "people24", "age" : 24, "classes" : 4 }
{ "_id" : ObjectId("5fa943987a7deafb9e54333e"), "name" : "people25", "age" : 25, "classes" : 5 }
{ "_id" : ObjectId("5fa943987a7deafb9e54333f"), "name" : "people26", "age" : 26, "classes" : 6 }
{ "_id" : ObjectId("5fa943987a7deafb9e543340"), "name" : "people27", "age" : 27, "classes" : 7 }
{ "_id" : ObjectId("5fa943987a7deafb9e543341"), "name" : "people28", "age" : 28, "classes" : 8 }
{ "_id" : ObjectId("5fa943987a7deafb9e543342"), "name" : "people29", "age" : 29, "classes" : 9 }
{ "_id" : ObjectId("5fa943987a7deafb9e543343"), "name" : "people30", "age" : 30, "classes" : 10 }
{ "_id" : ObjectId("5fa943987a7deafb9e543344"), "name" : "people31", "age" : 31, "classes" : 11 }
{ "_id" : ObjectId("5fa943987a7deafb9e543345"), "name" : "people32", "age" : 32, "classes" : 12 }
{ "_id" : ObjectId("5fa943987a7deafb9e543346"), "name" : "people33", "age" : 33, "classes" : 13 }
{ "_id" : ObjectId("5fa943987a7deafb9e543347"), "name" : "people34", "age" : 34, "classes" : 14 }
{ "_id" : ObjectId("5fa943987a7deafb9e543348"), "name" : "people35", "age" : 35, "classes" : 15 }
{ "_id" : ObjectId("5fa943987a7deafb9e543349"), "name" : "people36", "age" : 36, "classes" : 16 }
{ "_id" : ObjectId("5fa943987a7deafb9e54334a"), "name" : "people37", "age" : 37, "classes" : 17 }
{ "_id" : ObjectId("5fa943987a7deafb9e54334b"), "name" : "people38", "age" : 38, "classes" : 18 }
{ "_id" : ObjectId("5fa943987a7deafb9e54334c"), "name" : "people39", "age" : 39, "classes" : 19 }
{ "_id" : ObjectId("5fa943987a7deafb9e54334d"), "name" : "people40", "age" : 40, "classes" : 0 }
Type "it" for more
> 

  提示:建立測試數據可使用循環的方式,它這裏的循環和c語言中的循環是同樣的;在mongodb中查看數據,當數據量過多時,它不會一次性所有顯示,而是分頁顯示,每次默認顯示20條;鍵入it命令能夠顯示下一頁;

  在mongodb上建立索引,語法格式:db.mycoll.ensureIndex(keypattern[,options])或者db.mycoll.createIndex(keypattern[,options])

  在name字段上建立索引

> db.peoples.ensureIndex({name:1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> 

  提示:這裏的name是指字段名稱,而非索引名稱;後面的1表示升序,-1表示降序;

  查看索引

> db.peoples.getIndices()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "name" : 1
                },
                "name" : "name_1"
        }
]
> 

  提示:從上面返回的結果能夠看到,peoples這個集合上有兩個索引,一個名爲_id_,其對應的字段爲_id,以升序的方式排列;一個名爲name_1,其對應字段爲name,以升序的方式排列;默認不給索引取名,它就是字段名後面加下劃線,再加表示升序或降序的數字;以下所示

  刪除索引

> db.peoples.dropIndex("name_1")
{ "nIndexesWas" : 3, "ok" : 1 }
> db.peoples.dropIndex("age_-1")
{ "nIndexesWas" : 2, "ok" : 1 }
> db.peoples.getIndices()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
> 

  提示:刪除索引須要指定索引名稱,而且須要用引號引發來;

  在name字段上構建惟一鍵索引

  提示:建立惟一鍵索引,咱們只須要在建立索引時加上unique:true這個選項便可;所謂惟一鍵是指咱們指定的字段上的值必須是惟一的;若是在咱們在插入對應字段時和以前有的數據重複,此時會插入失敗;

  驗證:插入一條name字段值爲peoples23的數據,看看是否可以插入成功呢?

  提示:能夠看到當咱們在name字段上構建惟一鍵索引後,在插入name字段有相同值的數據時,它告訴咱們說插入的數據重複;不容許咱們插入;說明咱們建立的惟一鍵索引生效了;

  重建索引

> db.peoples.reIndex()
{
        "nIndexesWas" : 2,
        "nIndexes" : 2,
        "indexes" : [
                {
                        "v" : 2,
                        "key" : {
                                "_id" : 1
                        },
                        "name" : "_id_"
                },
                {
                        "v" : 2,
                        "unique" : true,
                        "key" : {
                                "name" : 1
                        },
                        "name" : "name_1"
                }
        ],
        "ok" : 1
}
> 

  提示:若是咱們要修改索引,能夠刪除從新鍵,上面的reIndex不能實現修改原有索引的屬性信息;

  構建索引並指定爲後臺構建,釋放當前shell

> db.peoples.createIndex({age:-1},{background:true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
> db.peoples.getIndices()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "unique" : true,
                "key" : {
                        "name" : 1
                },
                "name" : "name_1"
        },
        {
                "v" : 2,
                "key" : {
                        "age" : -1
                },
                "name" : "age_-1",
                "background" : true
        }
]
> 

  刪除全部手動構建的索引

> db.peoples.dropIndexes()
{
        "nIndexesWas" : 3,
        "msg" : "non-_id indexes dropped for collection",
        "ok" : 1
}
> db.peoples.getIndices()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
> 

  建立組合索引

> db.peoples.createIndex({name:1,age:1},{background:true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.peoples.getIndices()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "name" : 1,
                        "age" : 1
                },
                "name" : "name_1_age_1",
                "background" : true
        }
]
> 

  以name字段爲條件查詢數據,看看mongodb查詢過程

> db.peoples.find({name:"people1221"}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "testdb.peoples",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "name" : {
                                "$eq" : "people1221"
                        }
                },
                "queryHash" : "01AEE5EC",
                "planCacheKey" : "4C5AEA2C",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "name" : 1,
                                        "age" : 1
                                },
                                "indexName" : "name_1_age_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "name" : [ ],
                                        "age" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "name" : [
                                                "[\"people1221\", \"people1221\"]"
                                        ],
                                        "age" : [
                                                "[MinKey, MaxKey]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "node01.test.org",
                "port" : 27017,
                "version" : "4.4.1",
                "gitVersion" : "ad91a93a5a31e175f5cbf8c69561e788bbc55ce1"
        },
        "ok" : 1
}
>

  提示:從上面返回的結果能夠看到在本次查詢是IXSCAN(索引掃描),因此查找很快就返回了;同時也顯示了索引相關的信息;

  組合name和age字段條件查詢,看看是否命中索引?

> db.peoples.find({$and:[{age:{$lt:80}},{name:{$gt:"people200"}}]}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "testdb.peoples",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "$and" : [
                                {
                                        "age" : {
                                                "$lt" : 80
                                        }
                                },
                                {
                                        "name" : {
                                                "$gt" : "people200"
                                        }
                                }
                        ]
                },
                "queryHash" : "96038BC4",
                "planCacheKey" : "E71214BA",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "name" : 1,
                                        "age" : 1
                                },
                                "indexName" : "name_1_age_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "name" : [ ],
                                        "age" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "name" : [
                                                "(\"people200\", {})"
                                        ],
                                        "age" : [
                                                "[-inf.0, 80.0)"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "node01.test.org",
                "port" : 27017,
                "version" : "4.4.1",
                "gitVersion" : "ad91a93a5a31e175f5cbf8c69561e788bbc55ce1"
        },
        "ok" : 1
}
>

  提示:能夠看到咱們組合兩個字段作條件範圍查詢也是能夠正常索引掃描;

相關文章
相關標籤/搜索