什麼是索引
要理解索引,你需要在腦中有個畫面,這裏建議想象一本食譜,不是普通食譜,而是5000頁的厚重食譜,包含各種場合、菜餚和季節的食譜。雖然這個食譜很全,但是它有個缺點就是它是亂序的,第一頁可能是魚香茄子,第3000頁是紅燒茄子。
這還不是很要緊,關鍵問題是這本食譜沒有索引!
下面是你要問自己的第一個問題:如果沒有索引如何在食譜中找到糖醋排骨?唯一的選擇是一頁一頁翻過去,如果它在3892頁,你得要翻多少頁呀,最壞的情況是它在最後一頁,你就得把整本書都翻一遍。
解決辦法就是構建一個索引。
我們可以想到多種查找方法,其中食譜名稱可以作爲一個起點。如果建立一個按食譜名稱排列列表,隨後是其所在頁碼,那麼就是按食譜名稱對書建立索引了。其中的條目可能是這樣的:
紅燒排骨 : 45
豬肉餃子 : 320
醬蘿蔔 : 199
現在只要知道食譜的名稱就能通過該索引快速找到書中的任意食譜了,如果你只希望通過這種方式來檢索食譜,那就已經完事兒了。
但這是不現實的,比方說,你還會希望根據你冰箱裏面的食材查找食譜,或者是根據菜餚來進行查找。針對這種情況,你需要更多的索引。
這就產生了第二個問題,只有一個基於食譜名稱
的索引,如何能找到所有的排骨相關的食譜呢?缺少合適的索引,你仍然需要翻閱整本食譜–5000頁。在根據食材或者菜餚進行檢索時都是如此。
爲此我們需要構建另一個索引,這次是對食材進行索引,在這個索引裏面按照字母順序排列食材,每個食材都指向所有包含它的食譜所在的頁碼。最基本的食材索引
是這樣的
牛肉 : 301, 342, 785, 2310, 2456, 4310 ...
山藥 : 8, 20, 45, 78, 287, 1295, 4587 ...
豬肉 : 12, 124, 320, 890, 3719, ...
這是你希望的索引嗎?是不是很有用?
如果只是需要知道指定食材的食譜清單,這個索引就夠用了,但如果還希望在查找時包含任何任意其他與與食譜相關的信息,還是需要進行“掃描”–一旦知道牛肉的頁碼,你要翻到每一頁找到食譜的名字以及確定菜餚類型,雖然這比我們翻閱整本書要好,但是還遠遠不夠。
例如,一週前,你無意間在這本食譜裏面發現了一個很棒的雞肉料理食譜,但是卻忘了它的名字,你很想找到它然後做給你心儀的漂亮小姐姐喫。目前爲止,有兩個索引,一個時食譜名稱的索引,另一個是食材的索引。是否能將兩者結合起來,找到遺忘的雞肉食譜呢?
實際上,這是不可能的。如果從食譜名稱索引入手,但是卻不記得名字,檢索這個索引只比翻閱全書好一點點。從食材入手,則要檢查一系列頁碼,但是這些頁碼無法插入基於食譜名稱的索引。因此這種情況下只能使用一個索引,本例中食材的索引要更有用一點。
通常認爲一個查詢裏面要查找兩個字段,可以針對它們分離索引。有一個現成的算法:查找每個索引裏匹配項的頁碼,針對同時匹配兩個索引的名單掃面它們頁碼的交集。這樣可以減少掃描的總數。一些數據庫實現了這個算法,但MongoDB中沒有。就算它實現了,使用複合索引來查找兩個字段總是會比我剛纔描述的算法效率高。請記住每個查詢中數據庫只會使用一個索引,如果要對多個字段進行查詢,請確保有這些字段的符合索引。
那該怎麼辦?幸好我們有複合索引。
複合索引
目前爲止你建立的都是單鍵索引:它們只是對食譜的一個鍵進行索引。現在要爲整本食譜構建一個新的索引,不同之處這次是要使用兩個鍵。類似的使用多個鍵的索引成爲複合索引(compound index)。
該複合索引依次使用了食材和食譜名稱,可以這樣來標記它: 材料-食譜,其中的部分內容如下所示:
豬肉:
豬肉白菜燉粉條: 320
豬肉蛋卷: 3719
豬肉脯: 890
雞腿:
紅燒雞腿: 82
可樂雞腿: 3710
土豆燜雞腿: 2578
西紅柿
西紅柿炒雞蛋: 4827
西紅柿雞蛋湯: 2478
西紅柿牛腩: 489
這個索引的值對人是顯而易見的,現在可以根據食材進行查找,大致定位要找的食譜,哪怕只是記得名稱的開頭部分。對機器而言同樣很有價值,不用掃描該食材的全部食譜名稱了。
需要注意的是複合索引的的順序是很有講究的,假設將上述索引翻轉爲 食譜-材料,它能替代我們之前的索引嗎?
明顯不能!使用新索引,只要知道名稱,搜索就一定會定位到一個食譜,書中的一頁。如果是要查找含有香蕉食材的豬肉食譜,那就可以確定不存在這個食譜。如果進行翻轉之後,我們就必須要知道食譜的名稱,在去找食材,但是現實情況往往是我們知道食材卻不知道食譜名稱。
現在整本食譜有三個索引: 食譜
、食材
和食材-食譜
,也就是說我們可以安全地去掉食材這個索引了。爲什麼呢?
因爲對某一食材的索引可以使用食材-食譜索引,如果你知道食材,可以便利該複合索引,獲得包含它的食譜的頁碼列表。
總結
本文只是爲更進一步認識索引提供一個隱喻,從中可以認識到一些簡單的經驗法則,如下:
- 索引能夠顯著減少獲取文檔所需的工作量。沒有合適的索引,實現查詢的唯一途徑就是線性掃描整個文檔,直到滿足查詢條件爲止。這通常就是掃描整個集合。
- 解析查詢時只會使用一個單鍵索引(or是例外),對於包含多個鍵(比如食材和食譜)的查詢,包含這些鍵的複合索引能更好地解析查詢。
比如student表,對age和name都建立了索引,如果你查詢name=“zhangsan” and age = 20,也只是會使用其中一個索引
- 如果有食材-菜譜索引,可以去掉食材索引,也應該這麼做。更抽象一點,如果有一個a-b的複合索引,那麼僅針對a的索引就是冗餘的。但是如果b本身就是一個複合索引(b=c-d),那麼同時擁有a-b和a還是很有意義的。
- 複合索引裏鍵的順序也是很重要的。