0. 索引-----trie树
我们希望不用到叶子节点就能判断出是否在树中。
radix trie使用了垂直压缩,既如果分支只有一个孩子节点,则只在分歧处保存指针即可,不再进行无意义的延申。
倒排索引:
B+树并发协议,crab算法
crab算法提出了安全节点的概念,在父节点处会获得子结点的锁,如果子结点是安全结点,则可以释放父结点的锁。
crab算法插入删除操作时,首先会对根节点加w写锁,这会导致并行性下降。可以采用乐观假设,大部分操作都不涉及节点的分裂和合并。因此只需对根和中间节点加读锁,叶子节点加写锁即可。如果判断出需要分裂,则重启操作,从根节点加写锁重新尝试。
一. 如何执行查询操作(Operator Execution)
1. 排序算法
1.1 排序算法
(1)数据可以装进内存中,使用快排等算法。其中Top-K Heap Sort,非常适用于查询指定了ORDER BY且包含LIMIT。
(2)整体数据不能装进内存,使用外部归并排序。
- 排序阶段:需要对KV对进行排序,这里的Key是指排序比较的属性值。Value的选择有两种,Tuple(Early Materialization)和Record_ID(Late Materialization)
使用 B B B个buffer进行排序,代表一个run,排序完事后写回硬盘。 - 合并阶段:使用 B − 1 B-1 B−1个buffer,每个buffer都取自上阶段的归并结果,1个buffer存储结果,写满后写回硬盘。
K-way归并排序的代价: 2 N ∗ ( 1 + ⌈ log B − 1 ⌈ N / B ⌉ ⌉ ) 2N*(1 + \lceil{\log_{B-1}\lceil N/B \rceil}\rceil) 2N∗(1+⌈logB−1⌈N/B⌉⌉).其中 B B B代表可用的buffer数量,N代表数据page的数量,2代表读和写。
N ≤ ( B − 1 ) m × B N \leq(B-1)^{m} \times B N≤(B−1)m×B. 其中 m m m代表合并pass的数量。
1.2 基于索引的排序
(1)如果需要排序的key已经有了clustering index(文件中的记录也是按照search key的顺序排放)的话,就不需要外部归并排序算法了。假设是B+树,直接对叶子节点进行遍历即可得到排序完的结果。
(2)如果是Secondary Indices,虽然各叶子节点中的数据对应不同的物理block,但其实也可以进行优化,就是对叶子节点每个record_id先不去获取其在物理block,先记录下来其page_id,最后一起获取,从而避免多次随机读取的情况。
1.3 聚合函数
(1)排序的方法实现聚合函数。去除重复tuple、聚合函数、集合操作等都需要先进行排序。
(2)在实现聚合函数中,哈希方法通常效率更高。因为像Group by和 DISTINCT的操作,并不需要结果是有序的(排序做了额外工作)
-
可以放入内存的情况:根据Group by的Key,直接建立哈希表即可,这样相同的Key必会映射到同一位置,通过某个值对信息维护即可。针对没有提供Group by的查询语句,因为会建立一个空的Key值,这样就相当于默认都会映射到同一个位置。
-
无法放入内存情况:
- a) 哈希方法中的分区,假设有B个buffer可用,那么可以划分为B-1个分区,1个buffer用来输入数据,当分区满后写回磁盘。
这里假设: key值的分布是均匀的。(很多元素具有相同值的话循序扫描也许是更好的方式) - b) rehash, 将每个分区读入内存,使用哈希表进行统计。遍历完分区后,丢弃临时哈希表,将结果汇总。
- a) 哈希方法中的分区,假设有B个buffer可用,那么可以划分为B-1个分区,1个buffer用来输入数据,当分区满后写回磁盘。
2. join算法
(1) join操作输出的数据是什么?
Tuple:直接存储Tuple数据,好处是不需要再从硬盘读取数据,坏处是可能有很多后续操作不需要的数据
Record_ID:后续操作需要读硬盘才能获取完整的数据。更适合列式存储。
(2)使用哪种算法更好?
join操作中,索引可用情况下,拥有较少tuple的作为outer关系更合适
始终将较小的表(page/tuple数量小,视具体算法)作为outer relation
假设R和S表的page分别为
M
M
M和
N
N
N,tuple为
m
m
m和
n
n
n
2.1 Nested Loop Join
(1) NAÏVE :双重循环,对R表中的每个tuple都需要遍历一遍S表,总代价为
M
+
(
m
∗
N
)
M + (m*N)
M+(m∗N)
(2) Block: 对R表中的每个Block遍历S表,总代价为
M
+
(
M
∗
N
)
M + (M*N)
M+(M∗N)。
是匹配完整个Block的tuple才释放S表的Block。
如果有
B
B
B个buffer可用,
B
−
2
B-2
B−2给outer,1个buffer给inner,1个用来输出,总代价为
M
+
(
⌈
M
/
(
B
−
2
)
⌉
∗
N
)
M + (\lceil M/(B-2)\rceil *N)
M+(⌈M/(B−2)⌉∗N)
(3)Index:对S表建立索引,
C
C
C代表索引的代价,哈希表为
O
(
1
)
O(1)
O(1),B+树为
O
(
log
(
n
)
)
)
O(\log(n)))
O(log(n))),总代价为
M
+
(
m
∗
C
)
M + (m*C)
M+(m∗C),
2.2 Merge-join
(1)Merge-join算法,需要join的关系需要先根据合并属性值进行排序。(如果事先有序,则使用该算法比较好)
在关系S中收集具有相同JoinAttrs值的tuple。在关系R中去掉小于该JoinAttrs值的记录,找到相等的进行合并。
Merge-cost :
(
M
+
N
)
\left( M + N \right)
(M+N)
总代价为:Sort-cost + ( M + N ) \left( M + N \right) (M+N)
2.3 Hash Join
-
可以放入内存的情况:使用一个哈希函数 h 1 h_1 h1遍历outer relation映射join attributes建立哈希表,再遍历inner relation看哈希表中是否存在。
-
无法放入内存情况:
Bloom Filter概率模型,判断是否在集合内。如果回答为false,则可确定一定不在哈希表内。 为true时,则有可能在哈希表内。
采用bitmap来实现。一般有两个哈希函数,产生两个哈希值。哈希值再取模 K K K后,再插入bit数组中。其中 K K K代表bitmap的长度。
类似于聚合函数中的分区操作。递归划分:如果某个分区有很多记录,那么可用再额外的哈希函数进行再次划分总代价为: 3 ( M + N ) 3\left( M + N \right) 3(M+N)
根据哈希函数可以将JoinAttrs属性值映射为整数,因此就能对各关系进行划分,有相同属性值的被划分到同一个区域,相当于桶排序的思想。
再进行join时,只需要和对方的同一划分进行匹配即可。
3. processing model
如何去执行一个查询
(1)Iterator Model:每个operator都需要实现Next函数,向上一次返回一个tuple。容易实现输出控制,例如LIMIT 10,根节点只需调用10次Next即可。
(2)Materialization Model:向上一次返回整个关系
(3)Vectorized / Batch Model:向上一次返回一批次tuple
query-evaluation plan(query-execution plan)用来评估查询的一系列原语操作
1.Zone map提前计算好一些统计信息作为元数据,例如最大值最小值等,维护起来可能比较麻烦,更适合写少读多的负载
多索引scan是扫描多个索引,最终将结果整合在一起
3.1 Select算法
只有secondary index时,需要使用bitmap index scan,创建一个和relation的block数量相等的bitmap,扫描叶子节点时,设置相应的bit为1,扫描完毕后根据bitmap决定是否要跳过某些block。
materialized evaluation:每个操作的结果都需建立临时relation,并且写回磁盘。
pipelined evaluation:生成一个tuple,直接传递给下一个操作
3.2 并行process model
给定一个任务,如何分配给不同的worker
(1) inter-query 并行处理不同请求
(2)intra-query 将一个请求划分为不同任务并行执行,用来加速long-running queries。例如并行处理不同的operation
exchange operator负责汇总各fragment的结果
3.2.1 Parallel Sort
(1)Range-Partitioning Sort,类似MapReduce,Map负责读取磁盘数据对数据按照范围进行划分后,发给相应目标节点,目标节点排好序后拼接在一起即可。
(2)Parallel External Sort-Merge, 先在局部并行排序,合并排序结果
4 查询优化
选择更有效的查询计划。分为两个方面:
(1)关系代数层:生成相同集合(不用管顺序)的tuple的SQL语句是等价的,选择更高效的等价表达式。
(2)细节策略:如选择什么算法去执行某个operation,使用什么索引。
pipelined edges:中间结果直接发送给下一步操作,不写回硬盘
materialized edges:写回硬盘
(3)binder负责将SQL中引用的命名对象转换为某种内部标识符
将更具有选择性的条件放在最里面,可以过滤更多东西
4.1 cost-base search
使用成本模型用来估计执行的代价。选取不同plan中代价最少的。
statistics模块收集表中tuple的信息
(1)为简化分析,假设值是均匀分布,并且假定条件是独立的。
需要计算选择率,类似于概率计算的方式。
如果需要对列中出现的不同值维护其准确的出现次数,那么会占用很多内存。实际上,会将列中不同的值划分为不同的bucket。
4.2 join顺序优化
left-deep tree可以最小化写入磁盘的数量。既中间结果不需要写回到磁盘中。
动态规划: 枚举join算法,选择代价最低的,然后枚举表的扫描算法,选择代价最低的。
遗传算法: 对13个表及以上的join才使用。