天天看點

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

作者:墨也的筆
MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

再使用 MongoDB 時,如果完全沒有幫 collection 建立 index,那在搜尋時就會需要把整個 collection 翻過來一個一個找過。

就像你到小北百貨想要買一個「鍵盤」、一個「滑鼠」,如果店家沒有幫你分類成「計算機外圍」、「水電材料」、「衛浴用品」幾大類,加上鍵盤跟滑鼠也不見得放在一起,那可能要逛完一整圈才能找到你要的東西。是還好逛一圈小百百貨也沒多久,偶爾去逛一下還能發現一些便宜的好物XD。

但資料庫就不一樣了,尤其大資料的時代,連小公司都有幾百萬筆的資料。這時如果還一個一個慢慢找簡直就是曠日費時,是以怎麼讓 DB 吃到 index 是一件非常重要的事。

圖解 Index

在講 explain 之前先來說說 index 大概長成什麼樣子。假如果你今天開一間五金行,要用資料庫來記錄各個商品的庫存(inventory)狀況,那你的 collection 裡面就會有鍵盤、滑鼠、電風扇等等商品的價格跟數量

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

這時如果根據價格幫他們建 index,讓商品從最便宜排到最貴,那 MongoDB 并不會去修改資料在硬碟中的位置,而是會另外建一個 price 排序過後的清單并用名額指向資料位置。如此一來,當你想要找價格 699 元的東西時,Mongo 就會很快的從那個清單來找,再藉由名額拿到真實資料

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

因為 index 隻是另外建一個清單,是以想在同一個 collection 内建多個 index 也是沒問題的唷!譬如說我可以同時幫 price 跟 quantity 建 index,這樣再根據價格或數量做 query 時就都有 index 可以用

認識 explain

既然加 index 可以加速 query,那是不是拼命幫各個欄位加 index 就好?也不是這樣,重點在于你加的 index 有沒有被 query planner 用到。加了太多沒路用的 index 隻不過是占空間,而且還會拖慢寫入的速度。

我這邊有準備一個GitHub - LarryLuTW/mongo-explain-demo,裡面已經塞了一千筆資料,隻要跟着 README 用 Docker 跑起來就可以一起玩 explain 囉(我很少這麼認真寫 README 拜托大家去跑一下XD)

先來看看完全沒 index 的情況下,如果想找到價錢 699 的商品,Mongo 會怎麼做搜尋。方法很簡單,隻要在 find({ price:699 }) 後面加上explain() 就可以了(跑出來會有一大串,我們先看其中的 queryPlanner.winningPlan)

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

這邊有個關鍵stage: 'COLLSCAN',意思是這次 query 是把整個 collection 都找(scan)過一遍,可想而知效率一定非常的差。

如果想看更詳細的執行情況的話,可以帶參數explain("executionStats"),就可以看到這次 query 的過程總共檢查了 1000 筆資料(就全部啦XD),最後符合條件的資料卻隻有 1 筆,好像很可憐

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

加上 index

那要怎麼解決命中率隻有千分之一的窘境呢,那就是加一個{ price: 1 } index,意思是建一個 index,讓他根據 price 從小排到大

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

有了 index 後馬上來看看 explain,果然從 COLLSCAN 變成IXSCAN+FETCH 了。注意當有多個步驟(stage)時要從内層往外看,是以是先做 IXSCAN 再做 FETCH,意思是先從 price_1 index 中找到 699(Index Scan),再去把那些資料抓出來(Fetch)。是以我們剛新增的 price_1 是有幫助的。

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

Mongo 是怎麼選擇 query plan 的呢?

到這邊應該都對 explain 有點概念了,接着來講一個比較複雜的例子,看 Mongo 在多個 index 時,是怎麼選擇「他所認為的最佳的 query plain」

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

現在 price 跟 quantity 欄位上都有 index。先用腦袋瓜想一下,如果我今天想要補庫存,要找「庫存量隻剩一個,且價錢低于一萬的商品」,那怎麼使用 index 來找會最快呢?這邊有兩個方案:

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

方案一:先利用 quantity index 快速找到quantity == 1 的那些商品(假設有 200 個好了),再從 200 個中一個一個檢查,找出price < 10000 的商品

方案二:先利用 price index 快速找到price < 10000 的商品(隻是家五金行,一萬塊以下的商品會超多,是以假設有 800 個),再從 800 個中一個一個檢查,找出quantity == 1 的商品

比較一下,方案一光是第一步的 quantity == 1 就可以篩選掉不少資料,再從中找 price < 10000 應該很快,CPU 總共隻需要做 200 多次比較;而方案二的第一步 price < 10000 可以篩選掉的資料很少,加上第二步可能要做 800 多次的比較。是以沒意外的話應該是方案一比較好

但這畢竟是我們人工判斷的結果,之是以能這樣判斷是因為我們知道這是一家五金行的資料、也知道他的資料特性(一萬塊以下的商品會超多)。但對資料庫而言裡面就隻是一堆資料,是以 MongoDB 自己有一套方法來選出最佳方案。

不知道哪個方法快?那就跑跑看吧!

對 MongoDB 而言,因為不知道裡面的資料長什麼樣子,是以他會直接把各個可能的 plan 都試跑一下,看哪個 plan 最先回傳 101 筆結果,就會被選出來當 winning plan

以這個例子來說,我們可以用 explain("allPlansExecution") 來看各個 plan 執行的狀況(因為太詳細了,直接給大家看重點)

從下圖可以看到當方案一(先用 quantity index)抓到前 101 筆資料時,方案二(先用 price index )才剛抓到 15 筆資料而已,是以正如我們預期的,方案一确實比方案二快上許多

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

而 MongoDB 其實也不會每次都做方案之間的比較,他隻要比過一次就會把結果 cache 起來,避免每次 query 都浪費在做一樣的事情。

是以如果你在 production 上加了 index 之後 MongoDB 沒有馬上使用新的 index,那也不代表他不好,隻是可能要過個幾天才會被 MongoDB 用上

強迫推銷 index

雖然 MongoDB 大部分情況下都會選擇最佳方案,但在極少數的情況下也可能會選錯。是以如果你加了一個你覺得「超級好用、一定要用、不用會出大事」的 index,但 MongoDB 卻遲遲沒有用上,那就可以用 hint(index) 來強迫推銷 MongoDB 一定要使用你的 index

以剛剛的例子來說,MongoDB 認為最佳方案是優先使用quantity_1,跑出來的結果是:總共需要檢查 148 個 document 才能得到結果

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

但如果我覺得price_1 才是真正對他好的 index,那就可以在 query 時加上hint("price_1") 提醒(強迫)他用這個 index 來做搜尋,然後看看結果是不是跟你想的一樣好。

MongoDB explain 實戰-看看你的 index 是真有在做事,還是占空間而已

以這個例子來說,強迫他使用price_1 的結果就是totalDocsExamined 從 148 變成 988,速度直接慢了好幾倍,一點幫助都沒有

總結

到這邊大家對于 explain 的使用方式都有些概念了,透過 explain,你可以不斷檢驗 index 是不是真的适合你的應用,避免自己加了一堆「感覺有用」的 index,但其實隻是多占空間而已。

受限于篇幅,今天講到的 index 都是針對某個欄位的 Single Field Indexes — MongoDB Manual 而已,之後有機會再來講怎麼針對各種應用場景,客制化最适合的 Compound Indexes — MongoDB Manual 及 Partial Indexes — MongoDB Manual,讓你 query 的速度更上一層樓~