在工作中遇到数据优化的一点感想
一,前言
先做一下场景描述:在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分查询。
嗯,先这样。欢迎大家指正或提出好的思路。