MongoDB第七講索引基礎(chǔ)

不論是關(guān)系型數(shù)據(jù)庫還是NoQSL數(shù)據(jù)庫蹬挤,要獲取足夠高的查詢效率缚窿,都需要通過索引來控制,我們首先通過一個(gè)電影庫的實(shí)例來分析一下索引的基礎(chǔ)概念焰扳。

索引的基礎(chǔ)概念

電影庫的文檔結(jié)構(gòu)如下所示

{
    "_id" : ObjectId("5a3672cecff3930a19f5703c"),
    "name" : "異形1",
    "type" : [ 
        "科幻", 
        "驚悚"
    ],
    "year" : 1989.0,
    "nation" : "美國",
    "score" : 4,
    "info" : {
        "director" : "斯科特",
        "stars" : [ 
            "西格妮·韋弗", 
            "湯姆·斯凱里特"
        ]
    },
    "reviews" : [ 
        {
            "author" : "jake",
            "content" : "good movie",
            "score" : 4.0
        }, 
        {
            "author" : "leon",
            "content" : "bad movie",
            "score" : 2.0
        }
    ]
}

第一個(gè)要明白的問題是:為什么要使用索引倦零,假設(shè)電影庫中存在了50000條電影信息,我如果希望找到《七宗罪》這個(gè)電影吨悍,而這個(gè)電影剛好在地30222多條扫茅,那意味著,我們需要一條條的查找育瓜,直到找到這部電影葫隙,等于需要查找3w多次,這樣的效率顯然是很低的躏仇,但是如果我們在電影

庫中加入一個(gè)索引恋脚,有如下的信息

異形       0x11
阿甘正傳    0x13
.....
七宗罪     0x23
低俗小說   0x43

第一個(gè)值是name第二個(gè)值是硬盤的位置,這樣我們就可以快速的找到《七宗罪》這個(gè)電影焰手,索引其實(shí)和我們的書的目錄是一個(gè)道理糟描,但是對(duì)于印刷書籍而言,主要的索引都只是基于章節(jié)內(nèi)容來的(但現(xiàn)在的部分書籍书妻,特別是計(jì)算機(jī)類的書籍船响,在最后都會(huì)加入關(guān)鍵字的索引)。對(duì)于數(shù)據(jù)庫而言躲履,我們可以增加很多的索引见间,對(duì)于電影庫而言,我們除了需要根據(jù)名字來檢索崇呵,還會(huì)涉及到根據(jù)類型來檢索缤剧,所以我們可以為type來增加索引,一個(gè)type會(huì)對(duì)應(yīng)多個(gè)硬盤地址域慷,索引的樣子如下所示

懸疑     0x12,0x22,0x33,0x03,...,0x42
驚悚     0x12,0x22,0x37,0x2D,...,0x45
愛情     0x13,0x56,0x77,0xa3,...,0x44
....

這樣我們要根據(jù)電影的類型來查詢就容易得多荒辕。已上索引根據(jù)電影的名稱或者類型來查詢都非常的容易,但實(shí)際應(yīng)用中可能存在如下一種可能:我知道這個(gè)電影的類型犹褒,但是忘記的了電影的名字抵窒,但是如果讓我看到名字我就可以想起這個(gè)電影,如果基于以上的索引叠骑,會(huì)存在一些問題李皇。

首先如果根據(jù)name的索引,由于我們記不住名稱,所以查詢需要翻遍整個(gè)名稱掉房,顯然是不合理的茧跋,由于記住了類型,可以使用基于type的索引卓囚,但是我們不得不找到索引的位置之后瘾杭,還得一條條的讀取列表的信息,效率雖然會(huì)比基于name的高一些哪亿,但依然有改進(jìn)的空間粥烁。

我們可以對(duì)兩個(gè)字段同時(shí)建立索引,type和name蝇棉,先建立type之后建立name讨阻,此時(shí)就會(huì)得到如下的索引信息

懸疑
    七宗罪   0x23
    致命id   0x36
愛情
    阿甘正傳  0x13
    怦然心動(dòng)  0xA2
    ...

這樣就可以快速的檢索到我們想要的信息,這種索引稱之為復(fù)合索引篡殷,需要注意的是這種索引的順序一定要注意钝吮,如果先添加name之后添加type,就會(huì)得不到想要的結(jié)果贴唇,因?yàn)槲覀兏鶕?jù)不清楚電影的名字搀绣,所以究竟該創(chuàng)建什么索引一定要根據(jù)查詢需要來分析和設(shè)計(jì),如果盲目的添加太多的索引戳气,會(huì)增加內(nèi)容的維護(hù)的成本链患,效率反而會(huì)降低,我們要確保駐留在內(nèi)存的所有索引都是有效的才能提高查詢效率瓶您。

最后需要大家了解另外一點(diǎn)麻捻,按照上述實(shí)例,由于已經(jīng)創(chuàng)建了復(fù)合索引呀袱,而且是以type開始贸毕,那我們還有沒有必要再為type創(chuàng)建一個(gè)單獨(dú)索引呢?顯然是不需要的夜赵,因?yàn)橥ㄟ^這個(gè)復(fù)合索引已經(jīng)可以獲取type的值的明棍,但是有沒有必要為name創(chuàng)建一個(gè)單獨(dú)索引呢,這就是需要的寇僧,因?yàn)檫@個(gè)復(fù)合索引沒有辦法根據(jù)name來檢索信息摊腋。

索引的建立和效率

索引分為單鍵索引和復(fù)合索引,單鍵索引只會(huì)為一個(gè)key創(chuàng)建索引嘁傀,如果我們?yōu)殡娪暗膶?dǎo)演建立了索引兴蒸,又為電影的評(píng)分建立了索引,此時(shí)我們需要檢索某個(gè)導(dǎo)演的電影評(píng)分高于4分的電影细办。單鍵索引如下所示

索引名稱info.director 磁盤地址 向下遍歷 索引名稱 score 磁盤地址
斯皮爾伯格 0x12 3 0x12
大衛(wèi)芬奇 0xA2 4 0x11
克里斯托弗諾蘭 0xB1 5 0xA5
大衛(wèi)林奇 0x22 3 0xA0
... ... ... ....

當(dāng)執(zhí)行db.movies.find({info.director:"大衛(wèi)林奇",scroe:{$gte:3}}) 查詢時(shí)在具體查詢的時(shí)候橙凳,查詢優(yōu)化器首先會(huì)根據(jù)info.director進(jìn)行排序,之后根據(jù)score排序,然后取兩個(gè)的交集岛啸。

如果使用導(dǎo)演名稱和分?jǐn)?shù)來建立復(fù)合索引钓觉,結(jié)構(gòu)如下所示

索引名稱(director-score) 硬盤地址
斯皮爾伯格-3 0xAA
大衛(wèi)芬奇-3 0xA2
大衛(wèi)芬奇-4 0xB1
克里斯托弗諾蘭-5 0xB2
....

此時(shí)查詢優(yōu)化器通過director很快就可以定位到導(dǎo)演名稱,之后從這個(gè)位置開始檢索分?jǐn)?shù)值戳,效率就高很多议谷,但如果索引的順序反過來是先建立score再建立info.director,效率就會(huì)低得多堕虹,如下所示

索引名稱(director-score) 硬盤地址
3-斯皮爾伯格 0xAA
3-大衛(wèi)芬奇 0xA2
4-大衛(wèi)芬奇 0xB1
5-克里斯托弗諾蘭 0xB2
....

查詢優(yōu)化器首先會(huì)找到大于等于3分的所有數(shù)據(jù),然后一條條去獲取info.director中的數(shù)據(jù)芬首,這種效率比單鍵索引還要低很多赴捞,所以再次證明,如果要使用復(fù)合索引郁稍,一定要確定好順序赦政,否則只會(huì)使你的查詢效率變得更低。

MongoDB的索引類型

了解了索引的基本知識(shí)之后耀怜,我們需要了解MongoDB支持的幾種索引類型:

1恢着、唯一索引

唯一索引用來確保文檔中的key的唯一性,如果為某個(gè)字段設(shè)置了唯一索引之后财破,添加了相同的信息掰派,會(huì)拋出duplicate key的異常,創(chuàng)建索引的命令

db.user.createIndex({username:1},{unique:true})

2左痢、稀疏索引

按道理來說靡羡,索引應(yīng)該都是密集型的,特別對(duì)于關(guān)系數(shù)據(jù)庫而言俊性,由于有schema的限制略步,但是對(duì)于MongoDB而言,由于沒有schema的限制定页,每個(gè)文檔中可能有一些值是null的趟薄,有些key也是不存在的,此時(shí)如果為字段創(chuàng)建索引典徊,會(huì)為所有的null值都創(chuàng)建索引杭煎,這樣會(huì)增加索引的大小,一個(gè)比較特別的例子就是一些網(wǎng)站的留言宫峦,如果開啟了匿名留言岔帽,此時(shí)有很多用戶的id都是null,如果為用戶的留言信息增加索引导绷,將會(huì)存儲(chǔ)大量的null值的多余索引犀勒。這種方式就需要?jiǎng)?chuàng)建稀疏索引

db.movies.createIndex({"reviews.author":1},{unique:false,sparse:true})

第二種情況是如果我們?yōu)槟硞€(gè)key增加了唯一索引,但是這個(gè)key有可能存在null的情況,此時(shí)如果添加一個(gè)文檔贾费,第一個(gè)該key為null的可以添加钦购,但是第二個(gè)為null的就違反了這個(gè)約束,就無法添加褂萧。諸如用戶中如果有個(gè)字段foo是唯一的押桃,但是有可能存在null的情況,此時(shí)如果希望添加唯一索引导犹,必須設(shè)置該索引的sparse為true

db.user.createIndex({foo:1},{unique:true,sparse:true})

3唱凯、多鍵索引

MongoDB支持在一個(gè)數(shù)組上創(chuàng)建索引,此時(shí)會(huì)為每個(gè)數(shù)組中的元素都創(chuàng)建索引谎痢,只要檢索其中任意一個(gè)元素會(huì)得到多個(gè)索引入口磕昼。

{
    "name" : "異形1",
    "type" : [ 
        "科幻", 
        "驚悚"
    ]
}
{
    "name" : "七宗罪",
    "type" : [ 
        "驚悚", 
        "犯罪", 
        "懸疑"
    ]
}

為type創(chuàng)建了索引之后,當(dāng)檢索"驚悚"這個(gè)type時(shí)會(huì)得到多個(gè)索引入口节猿。

4票从、哈希索引

在MongoDB中默認(rèn)是使用字符來進(jìn)行排序的,MongoDB的索引存儲(chǔ)結(jié)構(gòu)是基于B-Tree的數(shù)據(jù)結(jié)構(gòu)滨嘱,這種結(jié)構(gòu)類似于二叉查找樹峰鄙,但是卻支持多個(gè)接點(diǎn),這種存儲(chǔ)方式如果整棵樹偏向某一個(gè)子節(jié)點(diǎn)太雨,會(huì)使得查詢效率變低吟榴,如:假設(shè)我們一username做了唯一索引,但結(jié)果這些用戶中基本都是s-z開頭的人特別多躺彬,這就會(huì)使得這顆子樹的節(jié)點(diǎn)偏多煤墙,查詢效率會(huì)有所降低,此時(shí)我們就可以設(shè)置這個(gè)索引為哈希索引宪拥,哈希索引會(huì)將每個(gè)值利用哈希算法來重新編碼仿野,讓整棵樹平衡,這樣可以提高查詢的效率她君。

另外就是對(duì)于objectId而言脚作,由于都是基于時(shí)間來生成的,看下面這些id

{
    "_id" : ObjectId("5a3672cecff3930a19f5703c")
}
{
    "_id" : ObjectId("5a3672cecff3930a19f5703d")
}
{
    "_id" : ObjectId("5a3672cecff3930a19f5703e")
}

這種id非常類似缔刹,在后面介紹的分布式時(shí)球涛,這些數(shù)據(jù)會(huì)存儲(chǔ)到一臺(tái)機(jī)器上,這是非常有危害的校镐,如果某個(gè)時(shí)刻有大量的插入請求亿扁,此時(shí)就意味著是一臺(tái)機(jī)器來承受所有的壓力,而哈希索引可以解決這種問題

db.users.createIndex({"_id":'hashed'})

5鸟廓、地理空間索引

MongoDB支持基于位置的經(jīng)緯度來建立索引从祝,諸如在找位置相關(guān)的信息時(shí)有所幫助襟己。

索引管理

MongoDB使用createIndex()方法創(chuàng)建索引,索引創(chuàng)建完成之后通過db.collection.getIndexes()可以查詢該collection中存在的索引信息牍陌。

>db.user.createIndex({username:1},{unique:true})
>db.user.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "document.user"
    },
    {
        "v" : 2,
        "unique" : true,
        "key" : {
            "username" : 1.0
        },
        "name" : "username_1",
        "ns" : "document.user"
    }
]

我們發(fā)現(xiàn)有兩個(gè)索引擎浴,一個(gè)是基于_id的,v表示版本信息毒涧,key表示對(duì)哪個(gè)字段添加索引贮预,name是索引的名稱,ns表示索引的名稱空間契讲,是基于document數(shù)據(jù)庫中的user這個(gè)collection來創(chuàng)建索引仿吞。

使用dropIndex(indexName)可以刪除一個(gè)索引

>db.user.dropIndex("username_1")
>db.user.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "document.user"
    }
]

下面我們將討論一下,究竟該在什么時(shí)候構(gòu)建索引捡偏,當(dāng)然最理想的肯定是在創(chuàng)建表的時(shí)候創(chuàng)建索引茫藏,這樣就會(huì)以增量的方式遞增,但是事實(shí)往往不是這樣理想霹琼,因?yàn)椴樵兊膯栴}都只會(huì)在數(shù)據(jù)量大的時(shí)候才會(huì)體現(xiàn)出問題,所以很多時(shí)候都需要在后期的項(xiàng)目運(yùn)營過程之中來調(diào)整和優(yōu)化索引凉当,這所帶來的問題就是枣申,新構(gòu)建索引會(huì)占用掉大量的時(shí)間,一般都建議在訪問量較小的時(shí)候來處理這個(gè)操作看杭,一般構(gòu)建索引的時(shí)候會(huì)占用寫鎖忠藤,此時(shí)如果希望用戶可以繼續(xù)訪問數(shù)據(jù),可以選擇后臺(tái)構(gòu)建索引楼雹。

db.test.createIndex({foo:1,bar:1},{background:true})

構(gòu)建索引時(shí)會(huì)消耗大量的內(nèi)存模孩,對(duì)項(xiàng)目的運(yùn)行的性能影響很大,此時(shí)我們可以考慮使用離線索引贮缅,離線索引一般用在分布式的環(huán)境中榨咐,通常可以將數(shù)據(jù)復(fù)制到一個(gè)接點(diǎn)谴供,在那個(gè)節(jié)點(diǎn)上進(jìn)行離線索引的構(gòu)建块茁,構(gòu)建完成之后將此接點(diǎn)切換為主節(jié)點(diǎn),繼續(xù)在另外一臺(tái)服務(wù)器上進(jìn)行索引的構(gòu)建桂肌。這些知識(shí)在后面的章節(jié)再來詳細(xì)介紹数焊。

另外就是如果進(jìn)行了大量的修改,刪除操作崎场,難免會(huì)存在很多索引碎片佩耳,這些索引碎片沒有用,但依然會(huì)占用內(nèi)存谭跨,所以此時(shí)可以通過reIndex重建索引干厚,重建索引時(shí)也是寫鎖定的李滴。索引使用時(shí)也需要格外慎重。

db.test.reIndex()

索引的基本操作就是這么多萍诱,但是我們需要掌握的技術(shù)是悬嗓,如何根據(jù)性能來設(shè)計(jì)和優(yōu)化索引,下一部分將會(huì)詳細(xì)介紹一套查詢優(yōu)化的方法裕坊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末包竹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子籍凝,更是在濱河造成了極大的恐慌周瞎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饵蒂,死亡現(xiàn)場離奇詭異声诸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)退盯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門彼乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渊迁,你說我怎么就攤上這事慰照。” “怎么了琉朽?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵毒租,是天一觀的道長。 經(jīng)常有香客問我箱叁,道長墅垮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任耕漱,我火速辦了婚禮算色,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孤个。我一直安慰自己剃允,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布齐鲤。 她就那樣靜靜地躺著斥废,像睡著了一般。 火紅的嫁衣襯著肌膚如雪给郊。 梳的紋絲不亂的頭發(fā)上牡肉,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音淆九,去河邊找鬼统锤。 笑死毛俏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饲窿。 我是一名探鬼主播煌寇,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逾雄!你這毒婦竟也來了阀溶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鸦泳,失蹤者是張志新(化名)和其女友劉穎银锻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體做鹰,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡击纬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钾麸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片更振。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饭尝,靈堂內(nèi)的尸體忽然破棺而出殃饿,到底是詐尸還是另有隱情,我是刑警寧澤芋肠,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站遵蚜,受9級(jí)特大地震影響帖池,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吭净,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一睡汹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寂殉,春花似錦囚巴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至村怪,卻和暖如春秽浇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甚负。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工柬焕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留审残,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓斑举,卻偏偏與公主長得像搅轿,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子富玷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容