【CMU 15-445/645 Database Systems】10 sorting & aggregations

490 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

前面已经讨论了在硬盘上如何存储和管理存储(disk manager),在内存中如何进行数据缓存(buffer pool manager)以及如何访问数据(哈希表存储中间数据,索引加速数据访问,并发控制策略用于多线程访问)。

Time to talk about how to execute queries.

Query plan

解析query,生成语法树。query plan是真实的执行流程,自底向上。最终从根节点输出的结果就是query的执行结果。

External merge sort

Queries often want to retrieve tuples in a specific order.

  • 消除重复(distinct)
  • 前面提到过,建立索引的一个优化方案是先把数据进行排序
  • 聚合操作(group by)

由于数据通常无法全部加载到内存中,所以常说的快排等等内部排序(数据记录在内存中的排序)是无法使用的。

归并排序是分治的思想。内部排序中也有归并排序,只不过外部排序中,将数据分片后各自独立的运行,然后再合并。

  • sorting:只排序当前块中的数据(按照内存能放下来的大小来分片),排序后写回到文件中
  • merging:将各自内部有序的文件合并成一个整体有序的大文件(类似合并k个有序数组)

2路归并排序

一些参数:

  • 2路:每次合并的是两个有序页面
  • 全部数据划分成N个pages
  • 每个线程占用B个buffer pages来存放输入输出数据。也就是每次可以加载B个页面到内存中。每次对一个page排序完后,写回到磁盘中
  • 在merge阶段,需要使用三个buffer pages,两个用来存放input(同时合并两个page),一个存放output

Double buffering optimization

内存中有更多的空闲buffer page的时候,可以在每次排序一个页面的时候,预先将下一个待排序的页面加载到内存中。

Using b+ trees for sorting

如果需要排序的某个表的某个属性上已经建立了索引,那么可以直接在这个索引上读取数据。但也要具体情况分析:

  • 如果是聚簇索引,也就是具体存放数据的索引,那么可以直接从叶子节点最左边依次读取到最右边。非常高效,避免了计算开销,并且磁盘的访问也是顺序的。
  • 如果是非聚簇索引则性能很差。因为通常来说,每一个叶子节点中的每一条记录都需要一次磁盘IO来读取数据。

Aggregations

聚合操作,将多个tuples合并成一个标量值。有两种方法,排序或哈希。

Hash aggregate

但是对于一些不需要数据有序的聚合操作(例如group by 和distinct),使用哈希是更好的方案。

  • 对ordering没有要求
  • 计算成本低很多

例如,前面提到的两个问题,哈希操作每次要检查当前记录是否在哈希表中已经存在一个entry

  • distinct:直接丢弃重复的数据
  • Group by:执行聚合计算(例如,通过每个entry对应一个数组来实现分组)

当然,还会面临到内存中是否能放得下的问题。

If the DBMS must spill data to disk, then we need to be smarter

External hashing aggregate

所以同样需要外部哈希。

  • phase1: partition,正常进行哈希,将key映射到对应的bucket中。某个bucket放满了就落盘。
  • phase2:rehash,对每个分区构建内存中的哈希表,然后做相应的计算。

看到这我有点迷了,看看后面的例子。

Partition

找到目标数据后,去掉无关列,对目标属性进行哈希

Rehash

以distinct操作为例,前一步将数据划分为了不同的partition,并写回到了磁盘上。对于每个分区数据:

  • 加载到内存中,并使用一个新的哈希函数将它映射到一个内存中的新哈希表中。
  • 遍历这个新哈希表的每个bucket(entry),将所有匹配目标要求的tuple取出来。

这基于每个partition都要能放入内存的假设。

如这个例子,distinct只需要在每个分区中取出一个数据(唯一的),所以使用新函数h2将每个分区的key映射到新的哈希表中就可以了。最终结果就是从新哈希表中依次取出每个元素。