开发 postgreSQL connector 支持 update/delete 操作的代码
背景与需求
当前 openLooKeng postgreSQL connector 不支持对数据表的 update 和 delete 操作,本文拟在 openLooKeng 框架的基础上,开发 postgreSQL connector 支持 update/delete 操作的代码。
分析
openLooKeng 语句执行流程:
一条 SQL 语句的执行的整体流程如下图所示:
这里面主要涉及到两种类型的节点,分别为 Coordinator 和 Worker 节点。
Coordinator 节点是用来解析语句,执行计划分析和管理 openLooKeng 的 Worker 节点,同时 Coordinator 跟踪每个 Work 的活动情况并协调查询语句的执行。Coordinator 为每个查询建立模型,模型包含多个 Stage,每个 Stage 再转为 Task 分发到不同的 Worker 上执行。
Worker 是负责执行任务和处理数据。Worker 从 Connector 获取数据。Worker 之间会交换中间数据。Coordinator 是负责从 Worker 获取结果并返回最终结果给 Client。
具体步骤为:
客户端通过协议发送一条查询语句给 openLooKeng 集群的 Coordinator;
Coordinator 接手到客户端传送到的查询语句,对语句进行解析、生成查询执行计划,根据查询执行计划一次生成 sqlQueryExecution->sqlStageExecution->HttpRemoteTask;
Coordinator 将每个 task 分发到所需要处理的数据所在的 Worker 节点上执行;
Task 通过 Connector 从数据源中读取数据,并执行 Source Stage 的 task;
处于下游的 Stage 中的 task 会读取上游 Stage 产生的输出结果,并在该 Stage 的每个 task 所处的 Worker 的内存中进行后续处理;
Coordinator 从发布的 task 后,持续地从 Singe Stage 中的 task 获取结果,并将结果缓存到 buffer 中,知道查询结束;
Client 从提交查询之后,会持续地从 Coordinator 获取本次查询的计算结果,知道获得所有的计算结果。
openLooKeng 内核调用
openLooKeng 的多源查询能力是通过 Connector 机制来实现的。其中 MySQL、postgreSQL 等 Connector 是主要是通过 presto-base-jdbc 中的代码来实现对 SQL 等数据源的读写。
其中 openLooKeng-spi 中主要定义了一些公共接口,供 openLooKeng-main 中的代码进行调用。
openLooKeng-base-jdbc 是数据库连接器的公共模块,对 openLooKeng-main 进行了实现和补充。其代码经过编译后,会对应加载到 mysql-plugin 等插件中,实现 mysql 对数据源的访问功能。base-jdbc 本身自己不会编译单独的插件。
通过断点调试,对 openLooKeng 接受一条 update SQL 语句的函数栈调用流程进行梳理总结,如下图所示。
结合源码分析,发现一条 update 语句在 connector jdbc 中的执行流程为 beginUpdate()->getConnection()->buildUpdateSql()->setUpdateSql()->finishUpdate()。在这个过程中需要一个字段作为行标识符来表示数据行。
其中 buildUpdateSql 将 openLooKeng 传入的 update SQL 语句转换为 connector 更新时的预编译语句,这里用到获取的行标识符作为 where 的筛选条件;setUpdateSql 作用是以 where 条件对数据行进行更新。
因此若想要实现 postgreSQL conncetor 的 update/delete table 功能,我们需要重写上述函数即可完成。
代码实现
方案设计及论证
通过前期的调研,共形成了以主键为标识符和以 ctid 字段为标识符的两种开发方案。
方案一:以主键为标识符
主键的定义为:表中经常有一个列或列的组合,其值能唯一地标识表中的每一行。由定义可知,主键对应到一张数据表中会设计到多种场景,分别为:没有主键、单字段主键以及多字段组合成的复合主键,因此需要考虑的情况较多。
方案二:以 ctid 字段为标识符
ctid 表示数据行在它所处的表内的物理位置,ctid 字段的类型是 tid。尽管 ctid 可以快速定位数据行,每次 vacuum full 之后,数据行在块内的物理位置就会移动,即 ctid 会发生变化,所以 ctid 不能作为长期的行标识符。因此如果要以 ctid 字段为标识符进行代码开发,需首先验证 ctid 在同一事务中进行 update/delete 操作是否会发生变化。
ctid 在同一事务中的变化情况
为了验证 ctid 字段能否用于 postgreSQL 的 update/delete 操作,我们分别在单线程和多线程的情况下去验证 ctid 在同一事务和不同事务的变化情况。
单线程
通过 jdbc 模拟了单线程,在线程中对数据表进行 update 操作,分别查询 update 前后的信息,观察 ctid 的变化情况。
autocommit=false 时,结果显示 ctid 未发生变化:
autocommit=true 时,结果显示 ctid 发生变化:
多线程
同理,通过 jdbc 模拟了两个线程,在两个线程中分别对数据表进行 update 操作,分别查询 update 前后的信息,观察 ctid 的变化情况。
autocommit=false 时,结果显示 ctid 未发生变化:
autocommit=true 时,结果显示 ctid 发生变化:
由验证实验结果来看,在同一事务中,update 未提交前或者回滚之后 ctid 是不变的。
开发方案选择
综合对比两种方案,其优缺点对比如下:
ctid 字段为行标识符的代码开发
autocommit 设置为 false,即保证能在同一事务中进行操作,ctid 是可以保证唯一性的。而 PostgreSql 的继承关系为
因此我们的主要开发重点在于重写 beginUpdate()-> getConnection()-> buildUpdateSql-> setUpdateSql ->finishUpdate()getUpdateRowIdColumnHandle() 用于获取行标识符
beginUpdate()
getConnection 重写 BaseJdbcClient 该方法,用于将只读模式设为 false,以便于进行更新操作
buildUpdateSql 将 openLooKeng 传入的 update SQL 语句转换为 oracle 更新时的预编译语句,其内部调用关系为
其中 buildRemoteSchemaTableName()获取数据表名,getColumnNameFromDataSource()用于从源数据中获取要更新的字段名,getColumnNameMap()用于构建字段名的映射关系,将原始字段统一转换为小写字母。具体实现如下图所示:
setUpdateSql 是以 where 条件对数据行进行更新,其内部调用关系为
其中 setStatement()用于对需要更新的字段根据数据类型进行赋值。具体实现如下图所示:
到此,我们完成了 postgreSQL connector 的 update 功能代码,而 delete 功能代码与此具有很大的相似性,此处不做展开讲述,具体可参考PostgreSqlClient实现。
功能展示
此处我们简单演示 postgreSQL connector 的 update/delete 特性。首先需对代码进行编译,运行 mvn clean install -DskipTests -T 1C 即可,编译完成之后,拷贝 hetu-server-1.5.0-SNAPSHOT 文件夹及 hetu-cli-1.50-SNAPSHOT-executable.jar 包。紧接着相关配置可参考社区文档。
在 web 界面输入 ip 及端口号,显示
数据表已经建好,具体实例为
针对 update,执行语句及结果包括
功能点 SQL 结果
版权声明: 本文为 InfoQ 作者【openLooKeng】的原创文章。
原文链接:【http://xie.infoq.cn/article/85c0704dc9ee3f6a7a759961d】。文章转载请联系作者。
评论