数据库内核-OrderBy排序算子

        少侠我们又见面了。今天我们一起聊聊作为数据库核心算子之一的OrderBy排序算子是如何实现的。OrderBy排序算子本质是对二维无序表数据按照排序规则(升序/降序、NULL前排/NULL后排)进行排序的一种算法实现。而对二维表排序的本质是一维数据排序的扩展。

OrderBy排序算子原理样例图

1 一维排序算法

        一维数据排序常见的算法有冒泡排序、选择排序、插入排序、快速排序、希尔排序、归并排序、基数排序、计数排序、堆排序、桶排序十大经典排序算法。不同算法在不同场景优劣不同。

        数据库内核非LIMIT场景一般采用快速排序排序。Postgre内核非LIMIT场景底层排序算子采用快速排序加冒泡排序实现,快速排序平均时间复杂度O(nlog),针对大数据量场景应用。而冒泡排序时间复杂度O(n^2),针对小数据量场景应用(Postgre数据量7行以下采用冒泡排序)。LIMIT场景一般采用堆排序实现。堆排序无需将所有数据有序输出,无需将所有数据量全局排序,相对性能较高。前边说到一般场景会采用堆排序实现LIMIT场景,为什么这么说,因为该排序算法还要考虑LIMIT的数量,LIMIT数据量较小时毋庸置疑第一选择堆排序,那么随着LIMIT数据量的增长堆排的性能会随之降低,可能需要其他算法进行优化。本文非LIMIT场景采用快速排序,LIMIT场景采用堆排序进行讲解其实现原理。

1.1 快速排序

        快速排序,又名挖坑填补排序。核心实现是选取无需序列中间值(理论上的中位数),将无序序列按中间值进行分组,小于中间值的全部放到左边,大于或等于中间值的全部放到右边,将两组数据再按照相同的方式进行分组,直到两个数据分成两组也就是两组相对有序,整个序列达到有序状态(此过程也是相同子问题求解思想)。

1. 中间值选取

        中间值选取的好坏直接影响排序的性能,当中间值选取相对极端会导致时间快速排序复杂度退化到O(n^2),因此可以对无需数据选取几个点进行计算其中位数作为整个序列的中间值进行排序。

        针对小数据量可以三个点选取方式进行计算平均值,计算如下:

                                                

        针对大数据量可以采用多个点选取方式计算平均值,计算如下:

                                                

                                                

                                                

                                               

2. 算法原理

快速排序数据推演图-1

1)选取无序序列中中间,此推演过程"15"为序列中间值,标记其中间值数据;两个指针标记序列首位元素,后边移动指针对序列数据进行排序;pRight指针指向的数据与中间值比较,大于中间值,向前移动;

快速排序数据推演图-2

2)pRight指针指向的数据与中间值比较,小于中间值,将pRight指针指向的数据覆盖到pLeft指针指向的数据内容;pLeft指针向后移动;

快速排序数据推演-3

3)pLeft指针指向的数据与中间值比较,大于中间值,将pLeft指针指向的数据覆盖到pRight指针指向的数据内容;pRight指针向前移动;

快速排序数据推演-4

4)pRight指针指向的数据与中间值比较,小于中间值,将pRight指针指向的数据覆盖到pLeft指针指向的数据内容;pLeft指针向后移动;

快速排序数据推演-5

5)pLeft指针和pRright指针重叠,将中间值覆盖到该指针指向的位置,完成一轮分组排序过程。后续过程以此类推使得最终所有数据整体有序。

1.2 堆排序

        堆排序我们常见的堆有两种堆,一种是大根堆,另一种是小根堆。顾名思义大堆大根堆就是对顶元素是序列中最大值;而小根堆对顶元素则是序列中的最小值。对大堆和小堆的选择依赖数据的正序输出还是反序输出。

1. 存储和逻辑结构

堆的存储结构图

        堆排序的数据存储结构本质是序列结构,为了模拟堆(二叉树结构)使其每轮处理的数据时树的高度的数量,同时可以输出前N个元素有序,后边的数据可以不进行处理,大大降低了时间复杂度。因此对一维序列按照索引的关系抽象的堆的数据结构。当前节点索引为:,其左节点索引为:,其右节点索引为:,当前节点与其左右节点的索引关系如下所示:

                                                    

                                                   

堆排序逻辑结构图

2. 算法原理

1)建堆过程

建堆过程数据推演图

        从堆的最后一个父节点向第一个节点依次调整,调整思想是选择当前节点元素、当前节点左节点和当前节点右节点中最小的节点与当前节点交换。也就是说当前节点数据要保证比两个子节点数据都小。依次处理到根节点,当前根节点一定是整个堆中最小的元素(本例为小根堆)。

2)调整过程

调整堆过程数据推演图-1

        将建好的堆堆顶元素与最后一个元素交换位置;将堆范围缩小,不包含最后一个元素,因为此时最后一个元素是有序数据,不需要参与堆的调整和排序过程。

调整堆过程数据推演图-2

        上述过程操作后需要进一步将堆顶元素进行调整。从当前节点开始依次与左右节点比较,交换调整使得当前节点保存的是三个节点(当前节点、左节点、右节点)中值最小,被交换的子节点再与其子节点继续按此方式调整,直到堆底为止。再反复以上两个过程直到满足输出LIMIT数量的数据。

2 二维排序算法

        数据库OrderBy排序目的是对二维表数据按照排序规则(升序/降序、NULL前排/NULL后排)进行排序。而二维表数据排序的基础是依赖一维数据的排序算法进一步扩展实现。本文将讲述三种对二维表数据排序的实现方式,分别是正向分组排序正向队列排序反向单列排序,接下来让我们一一道来。

2.1 反向单列排序

反向单列排序数据推演图-1
反向单列排序数据推演图-2
反向单列排序数据推演图-3

        反向单列排序与基数排序的思想类似,对排序列C1、C2、C3反向一次对每一列排序。先对C3列进行排序,再对C2列进行排序,最后再对C1列进行排序。对每一列排序的算法需要选择稳定排序才能保证反向对每一列排序后的数据整体有序。该算法实现方式相对简单,但性能最低,需要对每一列数据相邻的数据均要比较。

2.2 正向分组排序

        数据库多列排序本质是在前一列排序后,对前一列相等的数据范围内再按照下一列排序规则对下一列进行排序的方式。正向分组排序是将当前列相同数据范围内的数据划分为一个分组,然后再对当前分组内的数据按照下一列排序规则进行下一列排序,再将下一列排序好的数据进一步相等数据分组,再对生成的分组按照下一列排序规则进一步排序,依次类推将所有分组所有列的数据全部排完也就是整个数据全部有序。

正向分组排序数据推演图-1

1)将C1列按照C1列排序规则进行排序,本次排序规则所有列均按照升序进行排序。

正向分组排序数据推演图-2

2)根据第一列排好序的数据可以看出前四行数据全部相同,因此可以将前四行数据按照第一列的数据特性(相等)划分成一个分组,再该分组的基础上对C2列按照排序规则(升序)进行排序。

正向分组排序数据推演图-3

3)根据第二列排序序的数据可以看出前三行的数据全部相同,因此可以将前三行数据按照第二列的数据特性(相等)划分成一个分组,再该分组的基础上对C3列按照排序规则(升序)进行排序。

        该排序算法相对第一种排序算法(反向单列排序)性能相对较高。该算法排序过程是按照列进行排序,因此在列存数据库的应用场景具有较高性能,访问连续内存,无序跨大内存块读取,相性能较高,优势较大。

2.3 正向多列排序

正向多列排序数据推演图-1

        正向多列排序是将要二维表数据的每一行看做一个元素,整体相当于对一维数据进行排序,每一行的前后顺序是否交换需要对两行数据对应的每一列数据进行比较。以上图粉色对应的两行数据为例,所有列数据均升序排序,该两行数据C1列均为"200",对C2列进行比较"100"小于"300",因此第一行数据相对小于第二行数据,进行排序交换处理。

正向多列排序数据推演图-2

        将每行数据按照一个元素,对整个二维表数据当一维数据进行排序,使其整个二维表全部有序。该排序算子是按照行进行排序,因此在行存数据库的应用场景具有较高性能,访问连续内存,无序跨大内存块读取,相性能较高,优势较大。

3 总结

        总体来说数据库内核对二维表数据排序本质是对一维表数据排序的扩展。对一维数据排序一般采用快速排序堆排序实现;对二维数据排序采用正向分组排序正向多列排序反向单列排序实现。针对不同的数据内存模型(行存/列存)、不同的业务场景(非LIMIT/LIMIT场景)对不同算法的选择。结合具体应用场景选择一种最优的算法,能大幅度提升排序的计算性能。

你可能感兴趣的:(数据库内核-OrderBy排序算子)