天天看點

MongoDB · 特性分析 · 索引原理

當你抱怨mongodb集合查詢效率低的時候,可能你就需要考慮使用索引了,為了友善後續介紹,先科普下mongodb裡的索引機制(同樣适用于其他的資料庫比如mysql)。

比如上面的例子裡,<code>person</code>集合裡包含插入了4個文檔,假設其存儲後位置資訊如下(為友善描述,文檔省去_id字段)

位置資訊

文檔

pos1

{“name” : “jack”, “age” : 19 }

pos2

{“name” : “rose”, “age” : 20 }

pos3

{“name” : “jack”, “age” : 18 }

pos4

{“name” : “tony”, “age” : 21}

pos5

{“name” : “adam”, “age” : 18}

假設現在有個查詢 <code>db.person.find( {age: 18} )</code>, 查詢所有年齡為18歲的人,這時需要周遊所有的文檔(『全表掃描』),根據位置資訊讀出文檔,對比age字段是否為18。當然如果隻有4個文檔,全表掃描的開銷并不大,但如果集合文檔數量到百萬、甚至千萬上億的時候,對集合進行全表掃描開銷是非常大的,一個查詢耗費數十秒甚至幾分鐘都有可能。

建立索引後,mongodb會額外存儲一份按age字段升序排序的索引資料,索引結構類似如下,索引通常采用類似btree的結構持久化存儲,以保證從索引裡快速(<code>o(logn)的時間複雜度</code>)找出某個age值對應的位置資訊,然後根據位置資訊就能讀取出對應的文檔。

age

18

19

20

21

簡單的說,索引就是将<code>文檔</code>按照某個(或某些)字段順序組織起來,以便能根據該字段高效的查詢。有了索引,至少能優化如下場景的效率:

查詢,比如查詢年齡為18的所有人

更新/删除,将年齡為18的所有人的資訊更新或删除,因為更新或删除時,需要根據條件先查詢出所有符合條件的文檔,是以本質上還是在優化查詢

排序,将所有人的資訊按年齡排序,如果沒有索引,需要全表掃描文檔,然後再對掃描的結果進行排序

衆所周知,mongodb預設會為插入的文檔生成_id字段(如果應用本身沒有指定該字段),_id是文檔唯一的辨別,為了保證能根據文檔id快遞查詢文檔,mongodb預設會為集合建立_id字段的索引。

mongodb支援多種類型的索引,包括單字段索引、複合索引、多key索引、文本索引等,每種類型的索引有不同的使用場合。

上述語句針對age建立了單字段索引,其能加速對age字段的各種查詢請求,是最常見的索引形式,mongodb預設建立的id索引也是這種類型。

{age: 1} 代表升序索引,也可以通過{age: -1}來指定降序索引,對于單字段索引,升序/降序效果是一樣的。

複合索引是single field index的更新版本,它針對多個字段聯合建立索引,先按第一個字段排序,第一個字段相同的文檔按第二個字段排序,依次類推,如下針對age, name這2個字段建立一個複合索引。

上述索引對應的資料組織類似下表,與{age: 1}索引不同的時,當age字段相同時,在根據name字段進行排序,是以pos5對應的文檔排在pos3之前。

複合索引能滿足的查詢場景比單字段索引更豐富,不光能滿足多個字段組合起來的查詢,比如<code>db.person.find( {age: 18, name: "jack"} )</code>,也能滿足是以能比對符合索引字首的查詢,這裡{age: 1}即為{age: 1, name: 1}的字首,是以類似<code>db.person.find( {age: 18} )</code>的查詢也能通過該索引來加速;但<code>db.person.find( {name: "jack"} )</code>則無法使用該複合索引。如果經常需要根據『name字段』以及『name和age字段組合』來查詢,則應該建立如下的複合索引

除了查詢的需求能夠影響索引的順序,字段的值分布也是一個重要的考量因素,即使person集合所有的查詢都是『name和age字段組合』(指定特定的name和age),字段的順序也是有影響的。

age字段的取值很有限,即擁有相同age字段的文檔會有很多;而name字段的取值則豐富很多,擁有相同name字段的文檔很少;顯然先按name字段查找,再在相同name的文檔裡查找age字段更為高效。

當索引的字段為數組時,建立出的索引稱為多key索引,多key索引會為數組的每個元素建立一條索引,比如person表加入一個habbit字段(數組)用于描述興趣愛好,需要查詢有相同興趣愛好的人就可以利用habbit字段的多key索引。

mongodb除了支援多種不同類型的索引,還能對索引定制一些特殊的屬性。

0: 不開啟profiling

1: 将處理時間超過某個門檻值(預設100ms)的請求都記錄到db下的system.profile集合 (類似于mysql、redis的slowlog)

2: 将所有的請求都記錄到db下的system.profile集合(生産環境慎用)

通常,生産環境建議使用1級别的profiling,并根據自身需求配置合理的門檻值,用于監測慢請求的情況,并及時的做索引優化。

如果能在集合建立的時候就能『根據業務查詢需求決定應該建立哪些索引』,當然是最佳的選擇;但由于業務需求多變,要根據實際情況不斷的進行優化。索引并不是越多越好,集合的索引太多,會影響寫入、更新的性能,每次寫入都需要更新所有索引的資料;是以你system.profile裡的慢請求可能是索引建立的不夠導緻,也可能是索引過多導緻。

根據某個/些字段查詢,但沒有建立索引

根據某個/些字段查詢,但建立了多個索引,執行查詢時沒有使用預期的索引。

<a href="https://docs.mongodb.org/manual/core/indexes">mongodb索引介紹</a>

<a href="https://docs.mongodb.org/manual/reference/method/db.collection.createindex/">createindex指令</a>

<a href="https://yq.aliyun.com/articles/32434?spm=5176.100238.yqhn2.22.0cuwgh">mongodb sharded cluster</a>

<a href="https://docs.mongodb.org/v3.0/tutorial/create-a-unique-index/">唯一索引 (unique index)</a>

<a href="https://docs.mongodb.org/manual/core/index-ttl/">ttl索引</a>

<a href="https://docs.mongodb.org/manual/core/index-partial/">部分索引 (partial index)</a>

<a href="https://docs.mongodb.org/manual/core/index-sparse/">稀疏索引(sparse index)</a>

<a href="https://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/">database profiling</a>