# ClickHouse源码笔记6:探究列式存储系统的排序

### 1.执行计划

``````select * from test order by k1;
``````

image.png

### 2. 实现流程的梳理

• PartialSortingBlockInputStream的实现
PartialSortingBlockInputStream的实现很简单，咱们直接看代码吧：
``````Block PartialSortingBlockInputStream::readImpl()
{
sortBlock(res, description, limit);
return res;
}
``````

`SortDescription`是一个vector，每个成员描述了单个排序列的排序规则。比如
： null值的排序规则，是否进行逆序排序等。

``````/// Description of the sorting rule for several columns.
using SortDescription = std::vector<SortColumnDescription>;
``````
• sortBlock的函数实现

``````void sortBlock(Block & block, const SortDescription & description, UInt64 limit)
{
/// If only one column to sort by
if (description.size() == 1)
{
bool reverse = description[0].direction == -1;

const IColumn * column = !description[0].column_name.empty()
? block.getByName(description[0].column_name).column.get()
: block.safeGetByPosition(description[0].column_number).column.get();

IColumn::Permutation perm;
if (needCollation(column, description[0]))
{
const ColumnString & column_string = typeid_cast<const ColumnString &>(*column);
column_string.getPermutationWithCollation(*description[0].collator, reverse, limit, perm);
}
else
column->getPermutation(reverse, limit, description[0].nulls_direction, perm);

size_t columns = block.columns();
for (size_t i = 0; i < columns; ++i)
block.getByPosition(i).column = block.getByPosition(i).column->permute(perm, limit);
}
``````

``````    column->getPermutation(reverse, limit, description[0].nulls_direction, perm);
size_t columns = block.columns();
for (size_t i = 0; i < columns; ++i)
block.getByPosition(i).column = block.getByPosition(i).column->permute(perm, limit);
``````

``````ColumnPtr ColumnVector<T>::permute(const IColumn::Permutation & perm, size_t limit) const
{
typename Self::Container & res_data = res->getData();
for (size_t i = 0; i < limit; ++i)
res_data[i] = data[perm[i]];

return res;
}
``````

``````  if (needCollation(column, description[0])) {
const ColumnString & column_string = typeid_cast<const ColumnString &>(*column);
column_string.getPermutationWithCollation(*description[0].collator, reverse, limit, perm);
}
``````

• getPermutation的实现
所以，在ClickHouse的排序过程之中。`getPermutation`是整个排序算子实现的重中之重， 它是`Column`类的一个虚函数，也就是说每一个不同的数据类型的列都可以实现自己的排序逻辑。我们通过`ColumnVector`的实现，来管中规豹一把。
``````template <typename T>
void ColumnVector<T>::getPermutation(bool reverse, size_t limit, int nan_direction_hint, IColumn::Permutation & res) const
{
if (reverse)
std::partial_sort(res.begin(), res.begin() + limit, res.end(), greater(*this, nan_direction_hint));
else
std::partial_sort(res.begin(), res.begin() + limit, res.end(), less(*this, nan_direction_hint));
}
else
{
/// A case for radix sort
if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, UInt128>)
{
return;
}
}

/// Default sorting algorithm.
for (size_t i = 0; i < s; ++i)
res[i] = i;

pdqsort(res.begin(), res.end(), less(*this, nan_direction_hint));
}
}
``````

• 如果存在`limit`条件，并且列的长度大于`limit`，采用`std::partial_sort`进行`perm`的排序。
• 如果为数字类型，并且不为`UInt128`类型时，则采用`Radix Sort`计数排序来对`perm`进行排序。
• 如不满足前二者的条件，则使用快速排序作为最终的默认实现。

• MergeSortingBlockInputStream的实现
从名字上也能看出来，这里需要完成一次归并排序，来得到最终有序的排序结果。至于排序的对象，自然上面通过PartialSortingBlockInputStream输出的`Block`了。

``````Block MergeSortingBlockInputStream::readImpl()
{
/** Algorithm:
* - read to memory blocks from source stream;
*/

/// If has not read source blocks.
if (!impl)
{
{
blocks.push_back(block);
sum_rows_in_blocks += block.rows();
sum_bytes_in_blocks += block.allocatedBytes();

/** If significant amount of data was accumulated, perform preliminary merging step.
*/
if (blocks.size() > 1
&& limit
&& limit * 2 < sum_rows_in_blocks   /// 2 is just a guess.
&& remerge_is_useful
&& max_bytes_before_remerge
&& sum_bytes_in_blocks > max_bytes_before_remerge)
{
remerge();
}

if ((blocks.empty() && temporary_files.empty()) || isCancelledOrThrowIfKilled())
return Block();

if (temporary_files.empty())
{
impl = std::make_unique<MergeSortingBlocksBlockInputStream>(blocks, description, max_merged_block_size, limit);
}

return res;
}
``````

�由上面代码可以看到，MergeSortingBlockInputStream这部分就是不断从底层的PartialSortingBlockInputStream读取出来，并存储全部存储下来。最终读取完成之后，利用MergeSortingBlocksBlockInputStream类，完成所有Blocks的归并排序工作。而MergeSortingBlocksBlockInputStream类就是简单完成利用堆进行多路归并排序的过程代码，笔者在这里就不再展开了，感兴趣的同学可以自行参考MergeSortingBlockInputStream.cpp部分的实现。

### 3.要点梳理

1. ClickHouse的排序实现需要利用排序列生成对应的`perm`，最终利用`perm`完成每一个Block的排序。

2. 所以每一个不同数据类型的列，都需要实现`getPermutation``permute`来实现排序。并且可以根据数据类型，选择不同的排序实现。比如`radix sort`的时间复杂度为O(n)，相对快速排序的时间复杂度就存在了明显的优势。

3. 排序算法存在大量的数据依赖，所以是很难发挥`SIMD`的优势的。只有在`radix sort`下才些微有些部分可以向量化，所以相对于非向量化的实现，不存在太多性能上的优势。

### 4. 小结

OK，到此为止，咱们可以从Clickhouse的源码实现之中梳理完成列式的存储系统是如何实现排序的。

### 5. 参考资料

• 序言：七十年代末，一起剥皮案震惊了整个滨河市，随后出现的几起案子，更是在滨河造成了极大的恐慌，老刑警刘岩，带你破解...
沈念sama阅读 156,069评论 4 358
• 序言：滨河连续发生了三起死亡事件，死亡现场离奇诡异，居然都是意外死亡，警方通过查阅死者的电脑和手机，发现死者居然都...
沈念sama阅读 66,212评论 1 287
• 文/潘晓璐 我一进店门，熙熙楼的掌柜王于贵愁眉苦脸地迎上来，“玉大人，你说我怎么就摊上这事。” “怎么了？”我有些...
开封第一讲书人阅读 105,912评论 0 237
• 文/不坏的土叔 我叫张陵，是天一观的道长。 经常有香客问我，道长，这世上最难降的妖魔是什么？ 我笑而不...
开封第一讲书人阅读 43,424评论 0 202
• 正文 为了忘掉前任，我火速办了婚礼，结果婚礼上，老公的妹妹穿的比我还像新娘。我一直安慰自己，他们只是感情好，可当我...
茶点故事阅读 51,741评论 3 285
• 文/花漫 我一把揭开白布。 她就那样静静地躺着，像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上，一...
开封第一讲书人阅读 40,194评论 1 206
• 那天，我揣着相机与录音，去河边找鬼。 笑死，一个胖子当着我的面吹牛，可吹牛的内容都是我干的。 我是一名探鬼主播，决...
沈念sama阅读 31,553评论 2 307
• 文/苍兰香墨 我猛地睁开眼，长吁一口气：“原来是场噩梦啊……” “哼！你这毒妇竟也来了？” 一声冷哼从身侧响起，我...
开封第一讲书人阅读 30,289评论 0 194
• 序言：老挝万荣一对情侣失踪，失踪者是张志新（化名）和其女友刘颖，没想到半个月后，有当地人在树林里发现了一具尸体，经...
沈念sama阅读 33,923评论 1 237
• 正文 独居荒郊野岭守林人离奇死亡，尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
茶点故事阅读 30,251评论 2 240
• 正文 我和宋清朗相恋三年，在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
茶点故事阅读 31,775评论 1 255
• 序言：一个原本活蹦乱跳的男人离奇死亡，死状恐怖，灵堂内的尸体忽然破棺而出，到底是诈尸还是另有隐情，我是刑警宁泽，带...
沈念sama阅读 28,144评论 2 249
• 正文 年R本政府宣布，位于F岛的核电站，受9级特大地震影响，放射性物质发生泄漏。R本人自食恶果不足惜，却给世界环境...
茶点故事阅读 32,698评论 3 228
• 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹，春花似锦、人声如沸。这庄子的主人今日做“春日...
开封第一讲书人阅读 25,936评论 0 8
• 文/苍兰香墨 我抬头看了看天上的太阳。三九已至，却和暖如春，着一层夹袄步出监牢的瞬间，已是汗流浃背。 一阵脚步声响...
开封第一讲书人阅读 26,658评论 0 192
• 我被黑心中介骗来泰国打工， 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留，地道东北人。 一个月前我还...
沈念sama阅读 35,214评论 2 267
• 正文 我出身青楼，却偏偏与公主长得像，于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子，可洞房花烛夜当晚...
茶点故事阅读 35,159评论 2 258