前言
为了反对简单的 SQL 查问,并且提供更好的应用体验,咱们在最近的几个月里对 Databend 的 SQL planner 进行了大规模的重构。目前重构曾经靠近序幕,感兴趣的敌人能够通过批改 Databend 的 Session settings
SET enable_planner_v2 = 1
来启用新 planner 进行领先体验。
性能亮点
更加敌对的查问体验
无论是数据分析师还是开发人员,在编写 SQL 查问的时候总会遇到各种各样的报错。尤其是在 SQL 查问较为简单的状况下,排查报错成了许多人的噩梦(笔者自己已经批改过有数十个 JOIN 子句的 MySQL 查问,从此对 MySQL 的谬误提醒疾恶如仇)。
为了改善这方面的用户体验,咱们在新的 Planner 中引入了严格的语义查看环节,使得大部分的谬误能够在查问编译阶段就被拦挡。同时为了不便用户定位谬误的地位,咱们也引入了全新的谬误提醒算法。
当你的 SQL 查问应用了谬误的语法时(比方写错了关键字,或者脱漏了某些子句),Databend 会为你提供提示信息:
当你的 SQL 查问呈现语义上的谬误时(比方应用了不存在的 Column,或者 Column 具备歧义),Databend 也会为你指出谬误呈现的地位:
在编写简单查问时,仍然能够取得较好的体验:
反对 JOIN 查问与关联子查问
在新的 SQL planner 中,咱们反对了 JOIN 查问(INNER JOIN,OUTER JOIN,CROSS JOIN)与关联子查问,并且提供了 Hash Join 算法用以执行 JOIN 查问。
JOIN 查问的相干文档曾经公布在 https://databend.rs/doc/refer…,你能够查阅文档以理解 Databend 中 JOIN 查问的应用形式。
在 OLAP 查问中,JOIN 是十分重要的一部分。在传统的 星型模型 和雪花模型 中,咱们都须要通过 JOIN 查问将 维度表 与事实表 连接起来以生成后果报表。
TPCH Benchmark 是由 TPC 委员会制订的一套 OLAP 查问基准测试规范,用于评测数据库系统的 OLAP 能力。其中蕴含了 8 张表,别离是:
- Lineitem:产品我的项目
- Orders:订单信息
- Customer:顾客信息
- Part:零部件信息
- Supplier:供应商信息
- Partsupp:整机与供应商的关系表
- Nation:国家信息
- Region:地区信息
TPCH 中有 22 条简单的查问,对应不同的商业需要。这里以 Q9 查问为例,它的用处是计算指定 年度 和地区 的利润额,其中蕴含了大量的 JOIN 计算。在新的 Planner 中,咱们曾经能够反对该查问:
关联子查问同样也是 SQL 中的重要组成部分,通过关联子查问能够轻松示意简单的查问逻辑。TPCH 的 Q4 就是一个例子,它的用处是计算一段时间内 各优先级 的订单的 交付状况 。其中应用了 EXISTS 关联子查问来筛选 逾期收货 的订单:
目前 Databend 仅反对了关联子查问的简略执行,相干的查问优化工作仍在进行中,敬请期待。
全新架构
新的 SQL planner 中咱们对 SQL 解析的流程进行了从新设计,以撑持更加简单的语义剖析和 SQL 优化。在新的 SQL planner 中,一条 SQL 语句通过客户端发送到 databend-query server 后,会依照下图所示的程序由不同的组件进行解决,最终将查问的后果返回给客户端:
收到 SQL 查问后,Parser 组件会对其进行解析。在此步骤中如果遇到了语法错误则会间接将错误信息返回给客户端,解析胜利则会生成查问对应的 AST(形象语法树)。
Parser
为了提供更丰盛的语法分析性能和更好的开发体验,咱们开发了一套基于 nom Parser combinator 的 DSL (畛域特定语言) nom-rule,并基于该框架从新编写了 SQL Parser。
在这套框架下咱们能够十分轻松地定义一条 Statement 的语法,以 CREATE TABLE 语句为例,咱们能够应用 DSL 将其简略形容为:
CREATE ~ TABLE ~ #identifier ~ "(" ~ (#column_def)+ ~ ")" ~ ";"
优雅的语法大大提高了编写 Parser 的乐趣,欢送有趣味的敌人们进行尝试。
Binder
由 Parser 胜利解析出 AST 后,咱们会通过 Binder 对其进行语义剖析,并且生成一个初始的 Logical Plan(逻辑打算)。在此过程中,咱们会进行不同类型的语义剖析:
- Name resolution:通过查问 Databend Catalog 中相干的 Table, Column 对象信息,来查看 SQL 查问中援用的变量的合法性。并将非法的变量与对应的对象进行绑定,以进行后续的剖析。
- Type check:依据 name resolution 中拿到的信息,对表达式的合法性进行查看,并且为表达式寻找适合的返回类型。
- Subquery unnesting:将表达式中的子查问提取进去,翻译成关系代数的模式
- Grouping check: 对于含有聚合计算的查问,剖析是否在聚合函数以外援用了非聚合列
通过语义剖析,咱们能够排除掉绝大多数的语义谬误,并在编译阶段将其返回给用户,以提供最佳的谬误排查体验。
Optimizer
失去初始的 Logical Plan 后,优化器会对其进行改写和优化,最终生成一个可执行的 Physical Plan。
在新的 Planner 中,咱们引入了一套基于 Transformation Rule 的优化器框架(Volcano/Cascades)。通过定义一个关系代数子树结构的 Pattern 以及相干的 Transform 逻辑,即可实现一个独立的 Rule。
以简略的 Predicate Push Down 为例:
咱们只须要定义输出的 Plan 的 Pattern:
impl RulePushDownFilterProject {pub fn new() -> Self {
Self {
id: RuleID::PushDownFilterProject,
// Filter
// \
// Project
// \
// *
pattern: SExpr::create_unary(
Pattern {plan_type: RelOp::Filter,},
SExpr::create_unary(
Pattern {plan_type: RelOp::Project,},
SExpr::create_leaf(
Pattern {plan_type: RelOp::Pattern,},
),
),
),
}
}
}
并且实现一个进行转换的函数:
impl RulePushDownFilterProject {pub fn apply(&self, s_expr: SExpr) -> Result<SExpr> {let filter = s_expr.plan().into();
let project = s_expr.child(0).plan().into();
let result = SExpr::create_unary(
project,
SExpr::create_unary(
filter,
s_expr.child(0).child(0)
)
);
Ok(result)
}
}
Interpreter
通过 Optimizer 生成 Physical Plan 后,咱们会将其翻译成可执行的 Pipeline,并交由 Databend 的 Processor 执行框架进行计算。至此 Planner 的工作就告一段落,置信读者也对新 Planner 的架构有了一个初步的理解。更多的技术细节请关注咱们的后续文章。
将来布局
从头构建一个 SQL Planner 是一件非常具备挑战性的事件,然而通过从新的设计和开发,咱们能够找到最适宜零碎自身的架构与性能。在将来的一段时间里,咱们将继续欠缺和坚固新的 SQL Planner,性能方面则会重视于:
- Cost-based Optimization(CBO, 基于代价的优化)
- 分布式查问优化
- 更多的优化规定
目前,新的 SQL planner 的迁徙工作曾经靠近序幕,你能够通过该 issue 追踪进度。预计在七月份内所有的迁徙工作将会实现,届时咱们将会公布版本更新的布告,敬请期待。
对于 Databend
Databend 是一款开源、弹性、低成本,基于对象存储也能够做实时剖析的旧式数仓。期待您的关注,一起摸索云原生数仓解决方案,打造新一代开源 Data Cloud。
- Databend 文档:https://databend.rs/
- Twitter:https://twitter.com/Datafuse_…
- Slack:https://datafusecloud.slack.com/
- Wechat:Databend
- GitHub:https://github.com/datafusela…
文章首发于公众号:Databend