目前在一家在线教育公司工做,负责题库项目。题库项目因为以前设计的结构有问题,因此新构建了一版新题库。数据并无兼容旧题库。所以业务端在查询用户的练习记录的时候就须要调用新老题库的接口,而后合并。
分库分表最麻烦的就是排序分页查询了。题库和商城订单相似,通常只须要查询某个用户的练习记录,排序通常都是按照时间倒序。
老题库的练习记录为千万级,新题库为百万级。有些用户的练习记录可达到几万(免费的量就是大...)。所以用户的练习记录页面必须进行分页,并且要按照时间倒序排列,且支持各类筛选。前端
解决这个问题主要有两个大方向:
1.在存储练习记录的时候,单独存储一张汇总表,包含新老题库的练习记录的简略信息。查询的时候,先查询这张表,再分别到新老题库查具体信息。
2.优化查询。数据库
先看方法1,会增长建立练习的难度,不管这张汇总表存在新题库仍是老题库,都须要同步另外一个数据,这就涉及到数据一致性的问题了。因为不在一个库里,会产生分布式事务的额外问题。那可能有人会问,为何旧题库还有练习记录呢?新题库目前尚未支持完全部的考试类型,不支持的考试类型还在旧题库中作。并且即便是新的考试类型,少部分类型的题目暂时还在使用旧题库的。
此外,因为练习记录的查询条件较多,所以,这张汇总表里的字段,索引不会少,几千万条数据仍是比较占用空间的。
通过考虑决定放弃方法1,采用优化查询的方式。网络
先从最简单,也是效果最差的方案提及吧。这个全量查询其实也不是真正的全量查询。好比须要查询前10条,则只须要在新题库中取出前10条,旧题库中取出前10条。而后进行归并排序,找到真正的前10条。
可是当须要查询1000-1010条的时候,则须要在新旧题库中各取出前1010条,而后进行归并排序,找到第1000-1010条。由于可能旧题库的全部数据都在新题库以前,也可能一部分在新题库以前。
因此这种方案则须要在新旧题库中分别查询offset+limit条数据。显而易见,当offset太大的时候,会形成带宽的极度浪费,网络io拥堵,影响整个服务。分布式
有没有发现方案1中再查前10条的时候,其实只须要查20条,只有2倍的limit,由于limit不会太大,这没有形成多少的额外损耗。为何呢?
由于在查前10条的时候,知道一个额外条件,就是练习记录时间小于当前时间。那在查询11-20条记录的时候,这个时间是多少呢? 优化
如上图,假设在查前10条记录的时候,这10条记录由新题库的前n条数据和旧题库的前o条数据组成。新题库第n条数据对应时间为Tn,旧题库第o条数据对应时间为To。
那么再查第11-20条数据的时候,是否是能够新题库中查询时间小于Tn的前10条数据,在旧题库中查询时间小于To的前10条数据,而后对这两份数据进行归并排序,取出前10条。
因此只须要前端在查询下一页的时候,将上一页的Tn,To带过来。就能实现这样方式了。设计
优势:
1.实现逻辑较为简单;
2.效率高;
缺点: 不能随机访问(例如从第一页跳转到第5页),比较适合移动端的下拉加载。blog
这篇文章的题目为特殊的分库分表。正常的分库分表遇到此类问题通常都是怎么解决的呢?通常都是根据具体业务需求来分库分表,让数据分布有特色。好比业务中的查询条件都是查询某个用户的,则能够经过userId的哈希值来进行分库分表。查询条件都是查询某个月的,则能够按照建立时间来分表。
那这种新老数据的分布有什么特色呢?总结下。排序
1.最开始的数据都在老题库中;
2.某些新题库上线的考试,新记录大部分在新题库中,经过查询数据库,发现新旧比例约为50:1;
3.用户通常只作一个考试下的题目;
4.查询条件通常都是限定了考试。索引
能够总结出以下规律:用户的练习记录分布要考虑新题库是否已经上线他考的这门考试。 若新题库没有上线这门考试,则题目所有在旧题库中;
若新题库上线了这们考试,则新题库上线前用户的练习记录都在旧题库中,上线后,绝大部分在新题库。接口
能够看出难点在于新题库上线了的考试,假设新题库上线后,用户作的都是新题库的题目,是否是这个问题就简单多了呢?
那若是回到现实,用户在新题库上线后,还会使用旧题库,这时候主要是上图中哪一步的难度加大了呢?能够看到查询新旧题库的区间不会那么容易肯定了。由于这两者的前后关系不是那么明确了。
咱们先考虑第一种状况,新题库中的总量 >offset+limit。新题库的第offset+limit条数据Data在全局排名是多少呢?设此条数据的时间为T1,则须要查找旧题库中时间大于T1的数量n。Data的全局排名则为offset+limit+n。能够看出最终结果就在新题库的[0,offset+limit]和旧题库的[0,n]中。
能不能再把这个区间缩小呢?
首先新题库的offset+limit在全局排名是offset+limit+n,n>=0。右区间不能再精确了。而左区间呢?因为右区间的排名在offset+limit+n,咱们要保证左区间的全局排名小于等于offset。往前找limit+n条,这条数据在全局排名是多少呢?设这条数据的时间为T2,设T2到T1这个时间段有旧题库的数据m个,那从上图中能够清晰的看到全局排名是offset+limit+n-(limit+n)-m,也就是offset-m。
能够看到这个全局区间在[offset-m,offset+limit+n],包括了需求的[offset,offset+limit],咱们只须要在[offset-m,offset+limit+n]这个区间中找到[m,m+limit]即是最终结果。
能够看到上面的方式实际上是适合全部状况的。与以前总结的新旧题库的数据分布的特色没有关系。其实这些特色主要是影响了效率。
效率主要体如今两个方面,一个是带宽(查了多少数据),一个是速度。
先看看带宽方面吧。原本咱们须要查询limit条数据,如今变成了查新题库limit+n条,旧题库m条。
n是什么呢?新题库排名offset+limit的数据对应的时间为T1,T1前旧题库的数据个数。还记得咱们的数据分布特色吗?在新题库上线后,新题库的个数是旧题库的50倍。按照这个倍数算的话,n约为(offset+limit)/50,某些用户的练习个数可以达到1万,1万/50,也就是200。
m只是n的一部分,更小了。
新题库首先查询了一条数据,是根据时间进行排序的。旧题库查询了count,条件也包括时间。新题库根据时间排序,查询了区间内的数据。旧题库根据时间查询了数据。
能够看到一共四次查询。并且每次都是和时间有关。因此在必定要创建时间字段上的索引,或是以时间开头的复合索引。
接下来看下新题库数据不足的状况,这种状况下,咱们仍是仿照上面,如今新题库上面找。此次新题库没有offset+limit这么多条数据了,那咱们就查找新题库的最后一条数据Data,并查出新题库中的数据总数count。
首先咱们是能够获得Data的全局排名的,只须要根据Data的时间,到旧题库中查询一下排名在他前面的个数。
Data的全局排名有三种状况:
1.在M1中。这种状况,最终结果中没有任何新题库的数据。这个时候能够全部数据都在老题库中取。区间改成[offset-count,offset+limit-count]。 2.在M3中。这种状况,其实和上面讨论了半天的新题库数据充足的状况是一致的。 3.在M2中。这种状况,能够把前面的数据按照M3的作法求出来,后面的数据按照M1de方法求出来,最后拼接一下。
优势: 1.在新旧这种模型下,可以支持全部的分页需求。 2.效率较高 缺点: 1.只有在这种分布特色下,效率才会高。 2.实现较为麻烦。
在不支持随机访问的状况下,方案2无疑是最佳选择。 当数据有相似的分布特色时,且须要支持随机访问,能够选择方案3。