分表分页/跨库分页为什么这么难?

当业务数据达到一定量级(比如:mysql单表记录量>1千万)后,通常会考虑“分库分表”将数据均摊到不同的库或表中,这样可以大大提高读/写性能。但是问题来了,对于 select * from table limit offset , pagesize 这种分页方式,原来一条语句就可以简单搞定的事情,会变得很复杂,本文将与大家一起探讨分库分表后,分页问题的解决方案。

一、分表对分页的影响

比如下面有一张表,里面有这样8条记录(为简单起见,假设该表上只有1个id字段),数学上可以抽象成1个有序集合。

{1,2,3,4,5,6,7,8}

如果要取出上面红色标识的2,3这二条记录,limit 1,2 就行了。

现在假如分成2张表(即:原来的有序集合,拆分成2个非空有序子集合),一般来讲,有二种常用分法:

2.1 分段法(比如:有时间属性的数据,类似订单这种,可以1个月1张表)

{1 ,  2 , 3 , 4}

{5 , 6 , 7 , 8 }

沿用之前的limit x,y的思路,每个分表上 limit 1,2,会得到如下2个集合:

{2,3}

{6,7}

然后在内存中合并排序,再取前2条 {2,3,6,7} => {2,3} ,貌似看上去也符合预期(这个思路也称为归并),但这只是假象。当要取的分页数据落在不同的子序列上时,就能发现问题:

{1,2,3,4,5,6,7,8} 比如,我们要从3个位置开始(注:位置下标从0开始),连续取2个元素,即: limit 3,2

{1 ,  2 , 3 , 4} -> limit 3,2 -> {4}

{5 , 6 , 7 , 8 } -> limit 3,2 ->{8}

最后合并出来的结果是{4,8} 与正确结果 {4,5}相比,显然不对。

2.2 模余均摊法(即:关键字段对2取模求余数,根据余数决定分到哪个表)

{1 , 3 , 5 , 7 }

{2 , 4 , 6 , 8 }

归并排序的思路在分段法上行不通,对于取模均摊同样也不行,仍以 limit 1,2为例,原始序列取出来的结果是{2,3},如果用归并的思路:

{1,3,5,7} -> limit 1,2 ->{3 ,5}

{2, 4, 6, 8 } -> limit 1,2 -> {4, 6}

内存合并排序后,取第2个,最终结果为{3 , 4}

结论:不管分库分表采用什么分法,归并的思路,都无法解决分页问题。

二、全局法(limit x+y)

反思一下刚才的归并思路,本质上我们在每个子序列(即:分表)上limit x,y 时,取出来的数据就有可能已经产生缺失了。所以沈剑老师在他的文章"业界难题-“跨库分页”的四种方案"中,提了一个方案:把范围扩大,原sql上的limit x,y -> limit 0, x+y ,这样改写后,相当于把偏移量之前的所有数据全都取出来了(当然:这里面可能会有不需要的多余数据),然后内存中合并在一起,再取x偏移量后的y条数据。

还是用刚才的例子:

原序列:{1,2,3,4,5,6,7,8},需要取出limit 1,2 ,即:{2,3}

2.1 按分段法拆成2段:

{1 ,  2 , 3 , 4} -> limit 1,2 ->改写成 limit 0, 1+2 -> {1, 2 , 3}

{5 , 6 , 7 , 8 } -> limit 1,2 ->改写成 limit 0, 1+2 -> {5, 6 , 7}

将子序列合并排序-> { 1,2,3,5,6,7} -> 按原始偏移量 limit 1,2 ->{2,3} 正确

如果原序列中要取的数据,正好落在2个子序列上,{1,2,3,4,5,6,7,8},需要取出limit 3,2 ,即:{4,5}

{1 ,  2 , 3 , 4} -> limit 3,2 ->改写成 limit 0, 3+2 -> {1, 2, 3, 4} 

{5 , 6 , 7 , 8 } -> limit 3,2 ->改写成 limit 0, 3+2 -> {5, 6, 7,8}

将子序列合并排序-> {1,2,3,4,5,6,7,8} -> 按原始偏移量 limit 3,2 -> {4,5} 也符合预期。

2.2 取模均摊拆成2段

{1,3,5,7} -> limit 1,2 ->改写成 limit 0, 1+2 -> {1, 3 , 5}

{2,4,6,8} -> limit 1,2 ->改写成 limit 0, 1+2 -> {2, 4 , 6}

将子序列合并-> {1,2,3,4,5,6} -> 按原始偏移量 limit 1,2 ->{2,3} 正确

但是缺点也很明显:取出的记录太多了,比如 limit 100000,10 -> 改写后变成 limit 0, 100010 遇到海量数据,mysql的话,估计直接超时了。(注:一般情况下,需要用分库分表的场景,数据量必然很大,所以这个方法,实际中基本上没法用) 

参考文章:

https://juejin.im/post/5d1f52e46fb9a07eb3099bbf

https://shardingsphere.apache.org/document/current/cn/features/sharding/use-norms/pagination/

https://stackoverflow.com/questions/3927537/how-do-you-implement-sorting-and-paging-on-distributed-data

http://kmiku7.github.io/2019/08/01/Do-Pagination-With-Table-Database-Sharding/

https://segmentfault.com/a/1190000013225860?utm_source=tag-newest

https://mp.weixin.qq.com/s/h99sXP4mvVFsJw6Oh3aU5A?

猜你喜欢

转载自www.cnblogs.com/yjmyzz/p/12149737.html