一,前言
先做一下場景描述:在mongodb中,我們維護了一個A表,保留近2日的點擊信息。A表數據增長很快,每天300萬左右。這樣即使每日凌晨清理前天數據,到了晚上仍然會有近600萬數據。
有個業務需求:需要在不到1s的時間內根據uid查出A表對應的記錄。
問題:剛開始時每天也就幾十萬數據量,沒什么問題。現在一到晚上數據量漸增到600萬時,經常報查找超時。
二,我能想到的優化
很簡單,1,針對uid建立索引。uid是一個36位長的字符串。2,mongo的有一種查找叫 find_one 。即查找到第一次出現的即可。
3,分表。
三,分表的思想
分表是必須的,但如何分表呢。這就有技巧和經驗了。
經理和我關於怎么分表進行了一個小時的討論(后來發覺他早想好怎么分了,就是想看看我的思路和他的能不能一致)。事后覺得他的分析思路很不錯,特此記錄下。說點題外話,我覺得學習這事吧。自己學明白不一定是真明白,能把別人講明白才是真明白。所以在身邊沒有聽眾的情況下,寫博客就是比較好的辦法(哈哈,在自誇)。
1.進入正題:我們的目標是調高查詢速度。OK!造成查詢慢的原因有什么呢:1,數據量大。2,文件體積大。
所以600萬條60G的數據查起來會比600萬條60M的數據慢。有人會說,mongo存的是內存,mysql,oracle存的是文件。怎么會出現文件大小影響查詢速度呢。事實上不然,個人見證了這個A表成長的歷史,最早該表占用19G的大小。隨着業務量的增長,A表越來越大。眼睜睜看着他2G,2G的吃硬盤,最后到接近80G。而服務器的可用空間從開始的73%降到40%。所以,mongo用的是虛擬內存,虛擬內存實際是物理內存的抽象,多數情況下,出於方便性的考慮,訪問的都是虛擬內存地址,然后操作系統會把它翻譯成物理內存地址。Swap也是虛擬內存引申出的一種。這樣的話,你就涉及到地址偏移。文件巨大的情況下,查詢效率會下降。(個人理解,歡迎指正)
所以,我們可以試着給A表減肥。
2.按邏輯分表
事實上,處於數據完整性考慮。經理不希望縮減A表的字段。那么,另辟蹊蹺。我們針對查詢業務,專門拿出一個B表,專做查詢用。按邏輯分,A是日志表,B是查詢表。原先A表有10個字段,而且有的字段很大。現在B表僅需要4個字段。我估算過,相同數據量,A表是B表的三倍。這樣我們可以認為查詢效率提高3倍。該建索引照樣建索引啊。
3.按業務分表
因為必須要保留2天的數據。所不能簡單按日期,小時分表。但是,我們在B表有個字段C意義特殊,可以按照C字段分表。這樣又把B查詢表數據量減少一半。至此,我們相當於提高了3*2倍的查詢效率。相當於我只需要從100萬的數據中進行查找即可。
4.總結1
到此,經理的想法全部如上。先指出可取之處:1.再簡單,明顯的問題,也要先做分析。分析出文件大小會影響查詢效率,其實分表也是。各位肯定比我聰明,我當時沒想到。上來琢磨咋分表。沒考慮減少表字段的可能。這就是土作坊和正規軍的區別。(經理不混博客園,我拍馬屁也沒用)
2.分表也要有章程。先按邏輯分,再按業務分。
OK!想想,好像沒啥了。就這樣。
四,繼續深入研究
以上是我在公司討論研究出結果。優化也是按照上面的思路來的。優化完之后,果然系統運行正常,不再報錯。事后我琢磨一下,現在的成果相當於,優化成從100萬數據中查詢。如果以后生意好了,數據量又翻了6倍呢?咋整。我們現在這個分表好像已經物盡其用了,數據必須保留2天的。唯一的C字段也被我用來分表了。
下班我和室友討論了一下這個問題。其實還有辦法優化。感謝@fengbohello的技術支持。
前文描述到我們從B表查詢時是按照uid一個36位長的字符串進行查找的。嘿嘿,我們可以在uid身上做文章。uid大概這樣:"AB4A821C5DB3930C32A34000799F2D710E36"。
你在插入B表前,先做一步操作:table等於uid的每一位相加。uid每一位都是一個char,這樣你最后得到的肯定也是一個char。一個char有8bit。所以table就有2的8次方的可能。比如table='10011011'。這里要用加運算,別用與運算和或運算。因為這兩個運算得出0,1分布都是1:3或3:1。這樣得出的散列結果不會均勻分布。
我們的uid是隨機的,所以你最后將uid插入表時會隨機插入到table1='10011011'的表中。同樣,當你拿到uid做查詢時,做相同處理后,你就立即知道你要去table1中查詢對應的記錄。綜上所述,我們相當於根據uid進行散列,對B表又一次分成了256個分表。不但表變小了。而且400萬進行2分的話,相當於2的22次方。散列后,我減少了8次2分查詢。
嗯,先這樣。歡迎大家指正或提出好的思路。