火山模型的优缺点与优化实践 | OceanBase SQL优化

什么是火山模型

火山模型作为数据库领域内一个已相当成熟的计算模型,其核心思想是将关系代数中的每一种操作映射为一个独立的Operator,进而将整个SQL查询过程构建成一个Operator树结构。这一模型通过从根节点开始,逐级向下至叶子节点,递归调用next()函数。

例如 SQL:

复制代码
SELECT Id, Name, Age, (Age - 30) * 50 AS Bonus
FROM People
WHERE Age > 30

对应火山模型如下:

其中------

User:客户端;

Project:垂直分割(投影),选择字段;

Select(或 Filter):水平分割(选择),用于过滤行,也称为谓词;

Scan:扫描数据。

这里包含了 3 个 Operator,首先 User 调用最上方的 Operator(Project)希望得到 next tuple,Project 调用子节点(Select),而 Select 又调用子节点(Scan),Scan 获得表中的 tuple 返回给 Select,Select 会检查是否满足过滤条件,如果满足则返回给 Project,如果不满足则请求 Scan 获取 next tuple。Project 会对每一个 tuple 选择需要的字段或者计算新字段并返回新的 tuple 给 User。当 Scan 发现没有数据可以获取时,则返回一个结束标记告诉上游已结束。

为了更好地理解一个 Operator 中发生了什么,下面通过伪代码来理解 Select Operator:

复制代码
Tuple Select::next() {
    while (true) {
        Tuple candidate = child->next(); // 从子节点中获取 next tuple
        if (candidate == EndOfStream) // 是否得到结束标记
            return EndOfStream;
        if (condition->check(candidate)) // 是否满足过滤条件
            return candidate; // 返回 tuple
    }
}

火山模型的优缺点

可以看出火山模型的优点在于:简单,每个 Operator 可以单独抽象实现、不需要关心其他 Operator 的逻辑。

那么缺点呢?也够明显吧?每次都是计算一个 tuple(Tuple-at-a-time),这样会造成多次调用 next ,也就是造成大量的虚函数调用,这样会造成 CPU 的利用率不高。

知识点补习------虚函数
C++ 中用 virtual 标记的函数,而在 Java 中没有 final 修饰的普通方法(没有标记为 static、native)都是虚函数。
虚函数的重要特性是支持在子类中进行 override(重写),从而实现面向对象的重要特性之一:多态。

但是为什么之前的数据库设计者没有去优化这方面呢?是他们没想到吗?怎么可能?这个时候我们可能要考虑到 30 年前的硬件水平了,当时的 IO 速度是远远小于 CPU 的计算速度的,那么 SQL 查询引擎的优化则会被 IO 开销所遮蔽(毕竟花费很多精力只带来 1% 场景下的速度提升意义并不大)。

可是随着近些年来存储越来越快,这个时候我们再思考如何让计算更快可能就有点意思了。

优化方向

首先,考虑到大量虚函数的调用,那我能否写一个循环去执行 Operator 中的计算逻辑呢?执行完成后再向上传递,这样将之前的自上而下的拉模型改成了自下而上的推模型,databricks 的官方博客中提到他们使用一个简单的 benchmark 比较了火山模型与大学新手的手写代码的性能,性能差异如下:

这种手写代码的方式具有有将近 10 倍的性能提升,那么我们是否可以自动生成代码呢?只要自动生成的代码和高效的人肉代码几乎一致,那么提升的效果应该还是很明显的。

其次 ,火山模型中一次只取一条数据,如果每次取多条数据呢?貌似可行啊,因为可以将每次 next 带来的 CPU 开销被一组数据给分摊。这样当 CPU 访问元组中的某个列时会将该元组加载到 CPU Cache(如果该元组大小小于 CPU Cache 缓存行的大小), 访问后继的列将直接从 CPU Cache 中获取,从而具有较高的 CPU Cache 命中率,然而如果只访问一个列或者少数几个列时 CPU 命中率仍然不理想。另外,我们再想想什么时候可以做到取多条数据同时计算呢?当然是同一列的时候,所以针对的是列存的场景,因为输入是同列的一组数据,面对的是相同的操作,这正是向量寄存器干的事情,这是 CPU 层面计算性能的优化,因此称为向量化。并且如果每次只取一列的部分数据,返回一个可以放到 CPU Cache 的向量,那么又可以利用到 CPU Cache。

知识点补习:向量化
向量化计算就是将一个循环处理一个数组的时候每次处理 1 个数据共处理 N 次,转化为向量化------每次同时处理 8 个数据共处理 N/8 次,其中依赖的技术就是 SIMD(Single Instruction Multiple Data,单指令流多数据流),SIMD 可以在一条 CPU 指令上处理 2、4、8 或者更多份的数据。

思考

优化方向有了,即编译执行 (即代码生成)和向量化,但是在实际场景中我们要考虑得更多一些,比如在 SQL 很大时,是否会造成编译(生成代码)过慢呢?

相比火山模型,编译执行和向量化都使数据库查询执行性能得到了极大的提升,这两者之间相比又如何呢。首先这两个模型是不相容的,二者只能取其一。因为编译执行强调的是以数据为中心,在一个 CPU Pipeline 内是不会有物化的,但是向量化执行是拉模型,每次经过 next() 调用,向量的传递必须要进行物化。

总结

本文先通过简单的案例介绍了什么是火山模型,然后给出了优缺点,接着从缺点中结合内存的发展提出 CPU 层面的优化方案,最后介绍了编译执行和向量化这两种方案。后续,会继续介绍编译执行与向量化的具体性能表现以及二者如何结合。


OceanBase 云数据库现已支持免费试用,现在申请,体验分布式数据库带来全新体验吧 ~

相关推荐
摸鱼也很难5 小时前
buuctf sql注入类练习
sql·sql注入
一只小爪子6 小时前
SQL 语句说明
运维·服务器·数据库·sql·mysql
不辉放弃8 小时前
SQL 外键(Foreign Key)详细讲解
数据库·sql·oracle
小旺不正经16 小时前
txt、Csv、Excel、JSON、SQL文件读取(Python)
sql·json·excel
Full Stack Developme19 小时前
SQL 全文检索原理
python·sql·全文检索
鸿乃江边鸟1 天前
Starrocks的Bitmap索引和Bloom filter索引以及全局字典
大数据·starrocks·sql
GottdesKrieges1 天前
OceanBase企业版单机部署:obd命令行方式
数据库·oceanbase
lilye661 天前
程序化广告行业(80/89):近年发展动态与技术标准演进
sql·json·rabbitmq
阳光九叶草LXGZXJ1 天前
达梦数据库-学习-18-ODBC数据源配置(Linux)
linux·运维·数据库·sql·学习·oracle
好记忆不如烂笔头abc1 天前
oceanbase开启内存大页
linux·服务器·oceanbase