1. 概览
本节介绍查询执行相关内容,包括查询处理模型、查询树叶节点磁盘访问效率和 Where 表达式性能评估.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%202.png)
数据库中将查询算子组织成一棵查询树,数据从叶节点逐层传递到根节点,根节点的输出是查询最终的输出.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%201.png)
2. Processing Models
查询树的处理模型分为三种,分别是Iteration Model(就是常说的火山模型)、Materation Model 和 Vectorized/Batch Model. 没有适用于所有场景的模型,每种都有最适合的任务场景.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%203.png)
Iteration Model上层算子每次调用一次Next()函数后,下层算子都返回一个Tuple,返回的Tuple会被后面的算子继续处理,尽可能的在一次I/O中对Tuple进行很多的操作. 这种方法主要适用于早期计算机I/O性能非常低,内存特别小的场景. 调用Next()函数会调用一次虚函数,对于涉及大数据量的查询而言,多次调用虚函数会显著降低系统性能.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%204.png)
下图是一个Iteration Model示例,每个算子调用一次Next()函数时,下层算子仅返回一个Tuple.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%206.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%207.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%208.png)
众多数据库系统内部都实现了Iteration Model,进入内存的Tuple并不能总是能够一直被Pipline处理(当遇到pipline breaker时,如Join,Subqueries,Order by)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%209.png)
Materation Model模型流程与火山模型非常相似,区别在于当上层算子调用一次Next()函数时,下层算子一次性返回所有相关的数据(例如所有满足条件的行、列数据). 为了避免下层算子返回的数据量过大,上层算子可以尽可能提供多的过滤条件.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2010.png)
下图是一个Materation Model示例,每个算子调用一次Next()函数时,下层算子返回满足条件的所有Tuple.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2012.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2013.png)
Materation Model适合OLTP数据库系统,因为TP每次涉及到的数据一般较小,AP每次的中间结果集会很大. 这种方式相比 Iteration Model 减少了虚函数调用次数.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2014.png)
另外一种就是在 Iteration Model 和 Materation Model 之间的折中方案 Vectorized/Batch Model,每次调用Next()函数下层算子返回上层一个Batch的数据,Batch的大小取决于硬件配置. 这种方法适用于中间结果集较大的场景(例如OLAP系统).
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2015.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2016.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2017.png)
除了OLAT系统以外,Vectorized/Batch Model还适用于SIMD系统,利用指令集层面的并行能力一次处理一个Batch数据.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2018.png)
查询树的处理方向也可以从下往上处理,但是这种方式编写的代码不易于人的理解,但是可以进一步利用catch缓存(因为减彻底避免了虚函数调用)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2019.png)
3. Access Models
查询树的叶节点需要从磁盘获取第一步数据,可以顺序访问、借助索引甚至多个索引.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2020.png)
顺序扫描前面已经介绍过,逐个 Page 和 Tuple 扫描就行
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2021.png)
相关的优化方法有:
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2022.png)
其中,Zone Map指提前计算Page中数据集的相关属性统计信息,扫描Page前根据Zone Map检查Page是否包含相关的数据.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2023.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2024.png)
若不存在,则不用扫描该Page
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2026.png)
延迟物化技术前面也提到过,下层算子向上层算子仅提交tuple的地址信息,直到根节点处最终tuple确定时再根据地址信息填充相关属性的值,减少I/O开销.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2030.png)
若在需要检索的数据上(具体来说是属性上)已经建立了聚簇索引,那么直接根据索引来检索数据是最快的方式.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2031.png)
数据的分布情况决定了索引效果.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2032.png)
例如在下面的两个场景中,分别是利用dept索引和age索引性能最高,因为索引能够过滤的数据量最多.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2034.png)
当一个查询涉及的数据上有多个索引时可以进一步加速查询性能,PGSQL称这种技术为BitMap Scan.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2035.png)
对于下面的查询而言,age和CS上的两个索引直接通过取交集达到过滤掉不满足两个条件的数据,这种方式不仅数据量小,而且速度很快. 索引可以是哈希表或者布隆过滤器等.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2036.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2037.png)
若当前索引对于需要检索的数据而言不是聚簇索引,可以先根据当前索引记录下所有的Page ID,然后先对Page ID进行排序,然后再根据Page ID从小到大进行访问,这样catch利用率可以达到最高. 相当于原本索引一次性返回的Page ID无序,我们自己在获取到所有Page ID后手动排序一下就可以达到直接访问聚簇索引的效果.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2038.png)
4. Expression Evaluation
DBMS查询树的Where子句被称为expression tree.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2039.png)
DBMS会采用类似深度优先搜索的办法处理expression tree.
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2041.png)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2042.png)
若当某部分频繁调用或者值总是确定的,可以使用JIT(即使编译技术)优化执行性能,这样每次不用重复编译(对于Java解释型语言),也不用重复运行下面的逻辑(C++编译型)
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2043.png)
5. 总结
![](https://github.com/HiBo001/CMU445_pic/raw/main/12%20Execution/图片%2044.png)
关注我的微信订阅号,交流分布式系统相关技术:
![](https://img-blog.csdnimg.cn/ab044f5cfe2f4381b7002975ea93a099.jpg#pic_center)