添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

JDBC 外部表查询源码解析

导读:欢迎来到 StarRocks 源码解析系列文章,我们将为你全方位揭晓 StarRocks 背后的技术原理和实践细节,助你逐步上手这款明星开源数据库产品。本期 StarRocks 源码解析将介绍 JDBC 外部表查询源码解析

概述



StarRocks 支持通过 JDBC 接口访问外部表,外部表无需迁移数据,即可通过 StarRocks 完成查询。JDBC 外部表查询对数据库的类型没有过多限制,只要外部数据库提供 JDBC Driver 即可,像常见的MySQL/PostgreSQL/Oracle 等都可以同时查询。本文主要介绍 JDBC 外部表相关的实现。

相关概念


JDBCResource

用来描述 JDBC 资源相关的信息,由用户通过 DDL 创建,语法如下:

create external resource [RESOURCE_NAME]
properties (
    "type"="jdbc",
    "user"="user",
    "password"="password",
    "jdbc_uri"="jdbc:postgresql://127.0.0.1:5432/jdbc_test",
    "driver_url"="https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.3/postgresql-42.3.3.jar",
    "driver_class"="org.postgresql.Driver"
);

JDBCResource 包含几个重要的属性:

  • type:描述 resource 的类型,必须指定为 jdbc
  • user:访问外部表的用户名
  • password:访问外部表的密码
  • jdbc_uri:JDBC connection URL,需要满足对应数据库 connection URL 的语法
  • driver_url:JDBC Driver 的 http下载链接
  • driver_class:JDBC Driver Class 名字,每个数据库的 driver class 都不一样,需要按需设置

JDBCTable

描述外部表的元信息,由用户通过 DDL 创建,语法如下:

create external table [TABLE_NAME] (
(column_definition1[, column_definition2, ...]
) ENGINE=jdbc PROPERTIES (
    "resource"="resource_name",
    "table"="external_table_name"
);

JDBC 外部表的 column_definition 和普通的 OLAP 表一致,JDBC 外部表不支持索引,也不支持指定分区规则,建表时需要指定两个必要的 property:

  • resource:资源名称,和上述创建资源时指定的 RESOURCE_NAME 相同
  • table:对应的外部表表名

执行流程

下面详细介绍每个执行流程的实现细节

创建资源

JDBCResource 继承 Resource,用来描述资源相关信息,定义在 JDBCResource.java


创建资源时,首先根据 ResourceType 确定资源类型,然后通过 setProperties 设置属性,这部分实现在 Resource::fromStmt

    public static Resource fromStmt(CreateResourceStmt stmt) throws DdlException {
        Resource resource = null;
        ResourceType type = stmt.getResourceType();
        switch (type) {
            case SPARK:
                resource = new SparkResource(stmt.getResourceName());
                break;
            case HIVE:
                resource = new HiveResource(stmt.getResourceName());
                break;
            case ICEBERG:
                resource = new IcebergResource(stmt.getResourceName());
                break;
            case HUDI:
                resource = new HudiResource(stmt.getResourceName());
                break;
            case ODBC_CATALOG:
                resource = new OdbcCatalogResource(stmt.getResourceName());
                break;
            case JDBC:
                resource = new JDBCResource(stmt.getResourceName());
                break;
            default:
                throw new DdlException("Unsupported resource type: " + type);
        resource.setProperties(stmt.getProperties());
        return resource;
setProperties 阶段JDBCResource 会对每个需要的 property 进行校验尝试从 driver_url 下载 JDBC Driver 用来计算校验和并保存起来

setProperties 阶段,JDBCResource 会对每个需要的 property 进行校验,尝试从 driver_url 下载 JDBC Driver 用来计算校验和并保存起来

    protected void setProperties(Map<String, String> properties) throws DdlException {
        Preconditions.checkState(properties != null);
        for (String key : properties.keySet()) {
            if (!DRIVER_URL.equals(key) && !URI.equals(key) && !USER.equals(key) && !PASSWORD.equals(key)
                    && !TYPE.equals(key) && !NAME.equals(key) && !DRIVER_CLASS.equals(key)) {
                throw new DdlException("Property " + key + " is unknown");
        configs = properties;
        checkProperties(DRIVER_URL);
        checkProperties(DRIVER_CLASS);
        checkProperties(URI);
        checkProperties(USER);
        checkProperties(PASSWORD);
        computeDriverChecksum();


创建外部表

JDBCTable 继承 Table,描述 JDBC 外部表的相关信息,定义在 JDBCTable.java
建表的过程比较简单,这里不需要赘述,直接看 Catalog::createTable 的实现即可
需要注意的是,建表阶段,数据类型只要满足 StarRocks 的语法即可创建成功,不会去校验外表中的数据类型是否匹配,这个是在查询阶段处理。


查询外部表

生成查询计划

描述 JDBCScan 的逻辑计划和物理计划分别定义在 LogicalJDBCScanOperator PhysicalJDBCScanOperator ,在查询优化阶段,前者通过 JDBCScanImplementationRule 转换成后者。
为了尽量减少外部表和 StarRocks 之间传输的数据量,尽可能提升查询性能,我们会尝试将谓词和 limit 下推到外表。如果某些外部表的列只出现在谓词中而没有出现在投影中,那么也会对相应的列进行裁剪。
limit 下推使用的规则是 MergeLimitDirectRule ,列裁剪的规则是 PruneScanColumnRule
需要重点关注的是谓词下推策略,因为 StarRocks 支持的函数和运算符在外部表中不一定支持,如果它们出现在predicate 中,直接下推给外表的话会出现外表无法处理的查询。为了处理这种情况,StarRocks 采用了非常保守的策略,只尝试下推最简单的二元比较运算,in 和 between...and...运算符,其他的函数和运算符不会下推,由StarRocks 自己处理。
以下面两个查询为例,第一个查询中两个谓词都可以下推,而第二个查询中,ends_with 是个函数,不满足下推的条件,所以外表查询中的谓词只有 c_custkey > 20。

mysql> explain select * from customer where c_custkey > 10 and c_mktsegment in ('AUTOMOBILE', 'HOUSEHOLD') limit 10;
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Explain String                                                                                                                                                                              |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| PLAN FRAGMENT 0                                                                                                                                                                             |
|  OUTPUT EXPRS:c_custkey | c_name | c_address | c_city | c_nation | c_region | c_phone | c_mktsegment                                                                                        |
|   PARTITION: UNPARTITIONED                                                                                                                                                                  |
|   RESULT SINK                                                                                                                                                                               |
|   0:SCAN JDBC                                                                                                                                                                               |
|      TABLE: `customer`                                                                                                                                                                      |
|      QUERY: SELECT c_custkey, c_name, c_address, c_city, c_nation, c_region, c_phone, c_mktsegment FROM `customer` WHERE (c_custkey > 10) AND (c_mktsegment IN ('AUTOMOBILE', 'HOUSEHOLD')) |
|      limit: 10                                                                                                                                                                              |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
10 rows in set (0.01 sec)
mysql> explain select * from customer where ends_with(c_name, "25")=true and c_custkey > 20 limit 10;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| Explain String                                                                                                                            |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| PLAN FRAGMENT 0                                                                                                                           |
|  OUTPUT EXPRS:c_custkey | c_name | c_address | c_city | c_nation | c_region | c_phone | c_mktsegment                                      |
|   PARTITION: UNPARTITIONED                                                                                                                |
|   RESULT SINK                                                                                                                             |
|   1:SELECT                                                                                                                                |
|   |  predicates: ends_with(c_name, '25') = TRUE                                                                                           |
|   |  limit: 10                                                                                                                            |