30-12之MongoDB索引(2)---複合索引的坑
IT 鐵人賽 2016 mongodb
Lastmod: 2019-12-14

本文將會說明以下幾點。

  • 複合索引是啥~
  • 複合索引的運用與坑坑坑~

~ 複合索引是啥 ~

假設有下列資料。

{ "name" : "mark" , "age" : 20}
{ "name" : "mark" , "age" : 25}
{ "name" : "steven" , "age" : 30}
{ "name" : "max" , "age" : 15}

在上一篇文章中說到,如果要建立name的索引,是像下面這樣。

db.user.ensureIndex({"name" : 1})

這時mongodb就會大致上~將索引建成如下。

索引目錄         存放位置
["mark"]    -> xxxxxxxx
["mark"]    -> xxxxxxxx
["max"]     -> xxxxxxxx
["steven"]  -> xxxxxxxx

而所謂的複合索引事實上就是只是針對多個欄位建立索引,如下。

db.user.ensureIndex({"name" : 1 , "age" : 1})

mongodb就會建立索引如下。

索引目錄         存放位置
["mark",20]    -> xxxxxxxx
["mark",25]    -> xxxxxxxx
["max",15]     -> xxxxxxxx
["steven",30]  -> xxxxxxxx

~ 複合索引的運用與坑坑坑 ~

在前一篇文章中有說過,索引是把雙刃刀,建立的不好反而會浪費更多資源,而複合索引更是雙刃刀中連握把可能都有刀刃,以下舉個例子來說明~說明~

假設我們有以下的資料

{ "name" : "mark00" , age:20  }
{ "name" : "mark01" , age:25  }
{ "name" : "mark02" , age:10  }
{ "name" : "mark03" , age:18  }
{ "name" : "mark04" , age:26  }
{ "name" : "mark05" , age:40  }
{ "name" : "mark06" , age:51  }
{ "name" : "mark07" , age:20  }
{ "name" : "mark08" , age:51  }
{ "name" : "mark00" , age:30  }
{ "name" : "mark00" , age:100  }

這時我們要來思考一件事情,我們是要建立索引{ "name" : 1, "age" :1 }還是{"age":1,"name" :1 },這兩個是不同的,記好。

首先下列索引列表為 { "name" : 1, "age" :1 },索引的值都按一定順序排序,所以它會先依name的值進行排序,然後相同的name再按age進行排序。

db.user.ensureIndex({ "name" : 1 , "age" : 1 })

["mark00",20] -> xxxxxxx 
["mark00",30] -> xxxxxxx 
["mark00",100] -> xxxxxxx 
["mark01",25] -> xxxxxxx  
["mark02",10] -> xxxxxxx  
["mark03",18] -> xxxxxxx  
["mark04",26] -> xxxxxxx  
["mark05",40] -> xxxxxxx  
["mark06",51] -> xxxxxxx  
["mark07",20] -> xxxxxxx  
["mark08",51] -> xxxxxxx  

然後在來是{ "age": 1 , "name" : 1 }的索引列表。

db.user.ensureIndex({ "age": 1 , "name" : 1 })

[10,"mark02"] -> xxxxxxx
[18,"mark03"] -> xxxxxxx
[20,"mark00"] -> xxxxxxx
[20,"mark07"] -> xxxxxxx
[25,"mark01"] -> xxxxxxx
[26,"mark04"] -> xxxxxxx
[30,"mark00"] -> xxxxxxx
[40,"mark05"] -> xxxxxxx
[51,"mark06"] -> xxxxxxx
[51,"mark08"] -> xxxxxxx
[100,"mark00"] -> xxxxxxx

這兩種所建立出來的索引會完全的不同,但這在搜尋時會有什麼差呢,首先我們先來試試看下列的搜尋指令會有什麼不同。

情境1

我們執行下列的指令來進行搜尋,主要就是先全部抓出來,然後在根據age進行排序。

db.user.find({}).sort({"age" : 1})

首是{ "name" : 1, "age" :1 }的索引尋找過程與執行結果,memUsage : 660 代表有使用到內存進行排序。

執行過程

執行結果

再來看看{ "age": 1 , "name" : 1 }的執行過程與執行結果。

執行過程

執行結果

是的,明明都有建立索引,但只有{ "age": 1 , "name" : 1 }有利用到索引進行排序,而另一個還是需要用到內存來進行排序,主因就在於age先行的索引,它本來就依照age的大小先排序好,而name先行的索引,只先排序好name,後排序age,但後排序的age只是在同樣name下進行排序,所以如果是找『全部』的資料再進行排序,age先行較快。

情境2

db.user.find({"name" : "mark00"}).sort({"age" : 1})

先來看看 { "name" : 1, "age" :1 }的執行過程與結果,有使用索引進行尋找。

執行過程

執行結果

再來看看{ "age": 1 , "name" : 1 }的執行結果,也有使用索引進行尋找。

執行過程

執行結果

從上面兩張結果可以看出,他們都有使用到索引進行搜尋與排序,但name先行的索引只花了3次就得出結果,而age先行的卻花11次才得出結果,主要原因name先行的name已經排序好,三個mark00就堆在一起,要找到全部的mark00非常快,而age先行的就要全部慢慢找,才能找出全部的mark00

~ 結語 ~

從上面的實驗可知,在實際應用時{ "sortKey" : 1 , "queryKey" : 1 }是很有用的,也就是說如果某欄位很常被排序或是排序很耗時,在建立索引時請放至到前面也就是sortKey那位置。

還有另一點,當你建立{"name" : 1, "age" : 1}時,等同於建立了{"name" : 1}{"age" : 1},他們都可以用索引來搜尋name、age,但注意,這種建法排序索引只能用在上name

~ 參考資料 ~

comments powered by Disqus