暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

OceanBase1.0排序优化算法介绍

概述

本篇文章主要讲述一下分布式数据库中的排序优化算法,之所以叫做优化算法,是因为每种排序算法分别是针对不同的排序场景量身定制的,较基础的全排序有更好的性能。

sort的使用场景除业务本身显示指定order by之外,它还用于group by、distinct、join、union等算子的辅助实现,比如使用merge group by来实现groupby的时候需要下层额外分配一个sort,这部分是优化器生成逻辑执行计划时添加的,实现时与显式的sort无异。

sort在数据库中作为一个算子来实现,是一个需要物化的算子,物化的概念在这里是指需要将下层算子传递上来的数据缓存下来,而不是像一些其它算子拿到一行数据处理完就可以流水线一般一直向上传递,而它需要拿到所有的下层传递上来的数据才能进行本身的排序处理,排好之后才可以再向上层算子吐数据。如此,对于排序的性能影响较大的主要有两个方面,一个是排序本身,一个是深拷贝的过程。

针对这样的性能问题,考虑不同的业务场景,几种优化算法应运而生。本文主要介绍以下两种排序算法:

  • topn-sort

  • 分布式的多路归并排序

图片
(配图为堆排,摘自网络)

topn-sort

介绍

一条sql秒懂topn-sort的应用场景:
select * from t1 order by c1 limit 10;
topn的问题通常是指从大数据量中获得前n个满足要求的数据,可以是近似的也可以是精确的,近似的算法OceanBase也是有的,在OceanBase里面叫做topk。 这里主要讲精确的前n个,用于order by + limit 的场景,这是比较常用的,用户往往会关注最xx的事情,比如你想知道偷你能量最多是哪几个人。

实现

topn由n大小的堆实现,下层算子吐出来的数据未达到n行数据时,直接放入heap_array中,这里会做深拷贝,直到拿到了n行数据后,创建n大小的堆,小顶还是大顶根据排序要求来定。之后再拿到数据就会去更新这个堆,根据与堆顶数据的一次比较决定是直接丢掉还是放入堆中,直到更新完所有的数据。
以取前n大的数据为例: 

图片

性能提升与不足

  • 从算法时间复杂度来看,全排序为NlogN, topn为N1logn, N1最坏等于N。

  • topn不需要压缩存,基础全排序使用了压缩存储。数据均匀的情况下,topn只需要部分深拷贝。

  • topn现在未实现外排,后面会支持。根据实测,考虑时间和内存,在使用方面对n做了一定的限制,在小于某个比例和数值的时候才会使用。

分布式的多路归并排序merge-sort

介绍

多路归并排序也被应用于基础排序的外排中,对于数据量比较大内存中放不下的情况,会分成多份放在文件中,向上返回数据时按照多份数据源进行归并。这里的应用场景为分区表的排序,需要每个分区按照键值是有序的,每一个数据源对应一个分区的数据返回。
select * from t1 order by c1;
比如这样的一条简单的sql, t1是分区表,没有merge-sort的情况,需要把每个分区的数据拉上来,做一次全排序。使用merge-sort,如果c1本来就是主键或者存储层走的索引,每个分区原本就是有序的,那么只需要在上层做一次归并排序即可,如果分区无序,可以把order by c1下压到每一个分区去做排序,最后再做一次归并排序,两种情况都会比全排序的性能好。

实现

从中间结果管理器中拉取每一个分区的数据序列化好放在rowstore(可以压缩存储row的一个结构)中,对于每一个分区的数据可以看作一个数据源,每个数据源取一行组成一个k大小的堆,由上层算子驱动,迭代返回堆顶的数据,每返回一行数据,要从对应的数据源中补回一行,重新调整堆。 

图片

性能提升

  • 需要全排序的情况,分区有序,算法复杂度由NLOGN降到NLOGK, K指分区表的数量;分区无序,由NLOGN到KLOGN/K+NLOGK。上层有limit n时,只需要迭代n次就可以。

  • 所有的数据都会在rowstore中存储,所以处理过程中不需要深拷贝,与全排序相比,少了N行深拷贝的时间。

  • 没有merge-sort时,做order by的下压是没有意义的,实现merge-sort之后,可以将order by以及limit+order by下压到每个分区去做,某些场景大大减少了数据的拉取和处理。

sql使用展示

topn-sort

OceanBase (root@oceanbase)> create table t1 (a int, b int, primary key(a));
Query OK, 0 rows affected (0.05 sec)
OceanBase (root@oceanbase)> explain select * from t1 order by b limit 10;
| =====================================|ID|OPERATOR    |NAME|EST. ROWS|COST|
-------------------------------------|0 |LIMIT       |    |10       |1695|
|1 | TOP-N SORT |    |1000     |1685||2 |  TABLE SCAN|t1  |1000     |1180|
=====================================Outputs & filters:
-------------------------------------  0 - output([t1.a], [t1.b]), filter(nil), limit(10), offset(nil)  1 - output([t1.a], [t1.b]), filter(nil), sort_keys([t1.b, ASC]), topn(10)  2 - output([t1.a], [t1.b]), filter(nil),      access([t1.a], [t1.b]), partitions(p0)


merge-sort

OceanBase (root@oceanbase)> CREATE TABLE t (c1 INT NOT NULL AUTO_INCREMENT, PRIMARY KEY(c1, c2),
    ->   c2 INT)  PARTITION BY HASH(c2) PARTITIONS 4;
Query OK, 0 rows affected (0.08 sec)
OceanBase (root@oceanbase)> select * from t order by c1;
+------+----+
| c1   | c2 |
+------+----+
| -128 | 40 |
| -127 | 30 |
|    1 | 10 |
|    2 | 20 |
+------+----+
4 rows in set (0.01 sec)
OceanBase (root@oceanbase)> explain select * from t order by c1;
| ================================================|ID|OPERATOR               |NAME|EST. ROWS|COST|
------------------------------------------------
|0 |EXCHANGE IN MERGE DISTR|    |4000     |2880|
|1 | EXCHANGE OUT DISTR    |    |4000     |1180|
|2 |  TABLE SCAN           |t   |4000     |1180|
================================================

Outputs & filters:
-------------------------------------
  0 - output([t.c1], [t.c2]), filter(nil), sort_keys([t.c1, ASC])
  1 - output([t.c2], [t.c1]), filter(nil), hash(nil)
  2 - output([t.c2], [t.c1]), filter(nil),
      access([t.c2], [t.c1]), partitions(p[0-3])


总结

优化无止境,性能对数据库来讲至关重要,不放过每一个可以优化的性能点,精益求精,这也是OceanBase逐步迈入一个成熟数据库的必经之路。路漫漫其修远兮,吾将上下而求索~

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论