一、介绍

1 将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。

2 ShardingSphere 支持的结果归并从功能上分为遍历、排序、分组、分页和聚合 5 种类型,它们是组合而 非互斥的关系。从结构划分,可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的, 装饰者归并可以在流式归并和内存归并之上做进一步的处理。

3 由于从数据库中返回的结果集是逐条返回的,并不需要将所有的数据一次性加载至内存中,因此,在进行结果归并时,沿用数据库返回结果集的方式进行归并,能够极大减少内存的消耗,是归并方式的优先 选择。

4 流式归并是指每一次从结果集中获取到的数据,都能够通过逐条获取的方式返回正确的单条数据,它与数据库原生的返回结果集的方式最为契合。遍历、排序以及流式分组都属于流式归并的一种。

5 内存归并则是需要将结果集的所有数据都遍历并存储在内存中,再通过统一的分组、排序以及聚合等计算之后,再将其封装成为逐条访问的数据结果集返回。

6 装饰者归并是对所有的结果集归并进行统一的功能增强,目前装饰者归并有分页归并和聚合归并这 2 种类型。

二、遍历归并

它是最为简单的归并方式。只需将多个数据结果集合并为一个单向链表即可。在遍历完成链表中当前数 据结果集之后,将链表元素后移一位,继续遍历下一个数据结果集即可。

三、排序归并

1 由于在 SQL 中存在 ORDER BY 语句,因此每个数据结果集自身是有序的,因此只需要将数据结果集当前 游标指向的数据值进行排序即可。这相当于对多个有序的数组进行排序,归并排序是最适合此场景的排 序算法。

2 ShardingSphere 在对排序的查询进行归并时,将每个结果集的当前数据值进行比较(通过实现 Java 的 Comparable 接口完成),并将其放入优先级队列。每次获取下一条数据时,只需将队列顶端结果集的游 标下移,并根据新游标重新进入优先级排序队列找到自己的位置即可。

3 优先级队列

image.png

4 归并 源码
1)选择那种合并方式
image.png
image.png
2)流式合并
image.png

image.png

3)内存合并
image.png

四、分组归并

1 分组归并的情况最为复杂,它分为流式分组归并和内存分组归并。流式分组归并要求 SQL 的排序项与分组项的字段以及排序类型(ASC 或 DESC)必须保持一致,否则只能通过内存归并才能保证其数据的正确性。

2 流式分组归并与排序归并的区别仅仅在于两点:
1)它会一次性的将多个数据结果集中的分组项相同的数据全数取出。
2)它需要根据聚合函数的类型进行聚合计算。

3 对于分组项与排序项不一致的情况,由于需要获取分组的相关的数据值并非连续的,因此无法使用流式 归并,需要将所有的结果集数据加载至内存中进行分组和聚合。

4 当 SQL 中只包含分组语句时,根据不同数据库的实现,其排序的顺序不一定与分组顺序一致。但由于排序语句的缺失,则表示此 SQL 并不在意排序顺序。因此,ShardingSphere 通过 SQL 优化的改写,自动增加与分组项一致的排序项,使其能够从消耗内存的内存分组归并方式转化为流式分组归并方案。

五、聚合归并

1 无论是流式分组归并还是内存分组归并,对聚合函数的处理都是一致的。除了分组的 SQL 之外,不进行分组的 SQL 也可以使用聚合函数。因此,聚合归并是在之前介绍的归并类的之上追加的归并能力,即装 饰者模式。聚合函数可以归类为比较、累加和求平均值这 3 种类型。

2 比较类型的聚合函数是指 MAX 和 MIN。它们需要对每一个同组的结果集数据进行比较,并且直接返回其 最大或最小值即可。

3 累加类型的聚合函数是指 SUM 和 COUNT。它们需要将每一个同组的结果集数据进行累加。

4 求平均值的聚合函数只有 AVG。它必须通过 SQL 改写的 SUM 和 COUNT 进行计算。

六、分页归并

1 分页也是追加在其他归并类型之上的装饰器,ShardingSphere 通过装饰者模式来增加对数据结果集进行分页的能力。分页归并负责将无需获取的数据过滤掉。

2 ShardingSphere 的分页功能比较容易让使用者误解,使用者通常认为分页归并会占用大量内存。在分布式 的场景中,将 LIMIT 10000000, 10 改写为 LIMIT 0, 10000010,才能保证其数据的正确性。使用者非常容易产生 ShardingSphere 会将大量无意义的数据加载至内存中,造成内存溢出风险的错觉。其实,通过流式归并的原理可知,会将数据全部加载到内存中的只有内存分组归并这一种情况。

3 而通常来说,进行 OLAP 的分组 SQL,不会产生大量的结果数据,它更多的用于大量的计算,以及少量结果产出的场景。 除了内存分组归并这种情况之外,其他情况都通过流式归并获取数据结果集,因此 ShardingSphere 会通 过结果集的 next 方法将无需取出的数据全部跳过,并不会将其存入内存。

4 需要注意的是,由于排序的需要,大量的数据仍然需要传输到 ShardingSphere 的内存空间。因此, 采用 LIMIT 这种方式分页,并非最佳实践。由于 LIMIT 并不能通过索引查询数据,因此如果可以保证 ID 的连续性,通过 ID 进行分页是比较好的解决方案。

image.png

七、源码流程图

07 归并引擎 - 图8