在比较前沿的数据库中,比如cilckhouse,polar-x,TDSQL,都提到了一个比较新的词汇,叫向量化执行引擎。
clickhouse
polarDB-X
tdsql-A
向量化执行引擎似乎已经成为了主流数据库的版本之子。
那么向量化执行引擎是什么东西,做了哪些优化,能有什么收益呢?我决定来分析一下。
传统数据库执行器
早期数据库受限于硬件水平,IO、内存和CPU资源都非常昂贵,所以大多数数据库的执行器都采用的是传统的火山模型(经典的Volcano 模型)。
火山模型又称 Volcano Model 或者 Pipeline Model。
该计算模型将关系代数中每一种操作抽象为一个 Operator,将整个 SQL 构建成一个 Operator 树,从根节点到叶子结点自上而下地递归调用 next() 函数。
例如 SQL:
SELECT Id, Name, Age, (Age - 30) * 50 AS BonusFROM PeopleWHERE Age > 30
对应火山模型如下:
其实非常好理解。
- User:客户端。负责获取用户的sql,也负责发送给客户端sql的执行结果。
- Project:垂直分割(投影),选择字段。对应于sql为:“SELECT Id, Name, Age, (Age - 30) * 50 AS Bonus”,接收子节点数据后,通过处理,得到需要返回给上层的结果值。
- Select(或 Filter):水平分割(选择),用于过滤行,也称为谓词。对应于sql为:“WHERE Age > 30”,接收子节点数据后,过滤掉不符合条件的数据。
- Scan:扫描数据。将数据从存储层拉到计算层。比如将People的表数据从磁盘拉到内存。对应sql为:“FROM People”
数据库执行器的发展
火山模型的优劣
早期数据库受限于硬件水平,IO、内存和CPU资源都非常昂贵,比如计算层的数据一多,内存容易爆掉,所以火山模型采用每次只计算一行数据的方式,极大缩减了内存使用量。
火山模型的优点:简单易用,每个 Operator 可以单独抽象实现、不需要关心其他 Operator 的逻辑。
Volcano模型简单灵活,火山模型将更多的内存资源用于IO的缓存设计而没有优化CPU的执行效率,为什么之前的数据库设计者没有去优化这方面呢?
当时的 IO 速度是远远小于 CPU 的计算速度的,那么 SQL 查询引擎的优化则会被 IO 开销所遮蔽(毕竟花费很多精力只带来 1% 场景下的速度提升意义并不大)。
这在当时的硬件基础上是很自然的权衡。
但现在今时不同往日,硬件性能大力发展,在大数据等现代环境场景下,火山模型的弊端逐渐显露。性能表现差强人意。当需要处理的数据量增大时,具有显著的缺陷。
火山模型的缺点:查询树调用 next() 接口次数太多,并且一次只取一条数据,CPU 执行效率低;而 Joins, Subqueries, Order By 等操作经常会阻塞。
执行器为了适应复杂的表达式结构,计算一条表达式往往需要引入大量的指令;对于行式执行来说,处理单条数据需要算子树重新进行指令解释(instruction interpretation),从而带来了大量的指令解释开销。
据论文 MonetDB/X100: Hyper-Pipelining Query Execution 统计,在MySQL执行TPC-H测试集的 Query1 时,指令解释就耗费了90%的执行时间。
究其原因。主要有如下几点:
- 每次 next 都是一次虚函数调用过程是被动拉数据,编译器无法对虚函数进行inline优化,同时也带来分支预测的开销,且很容易预测失败,导致CPU流水线执行混乱。
- Volcano Style的代码对数据的局