OceanBase 源码解读(三)SQL 的一生(下)

2024年 5月 7日 43.4k 0

OceanBase 源码解读(三)SQL 的一生(下)-1

本文为 OceanBase 数据库源码解读系列文章的第三篇,主要介绍 OceanBase 数据库中一条 SQL 的执行流程,包括接收、处理、返回结果给客户端的过程。

OceanBase 源码解读(三)SQL 的一生(下)-2

src/observer 目录下,有三个子目录。其中,omt 中的 mt 表示 multi-tenant,里面实现了 observer 线程模型的抽象 worker,每个租户在其有租户的节点上会创建一个线程池用于处理 SQL 请求。virtual_table 目录下是 sys 租户各个 __all_virtual 虚拟表的实现,“虚拟表”是 OceanBase 土话(其实是 view),它把一些内存数据结构抽象成表接口暴露出来,用于诊断调试等。mysql 目录就是 MySQL 协议层,实现了 MySQL 5. 6兼容的消息处理协议。

OceanBase 源码解读(三)SQL 的一生(下)-3

除了建立和断开连接,MySQL 协议大多是简单的请求响应模型。每种请求类似一个 COM_XXX 命令,每种命令的处理函数对应在本目录有一个文件(类)。比如最常用的 COM _QUERY 表示一条 SQL 请求,处理类就位于 obmp_query.h/cpp 中。一般典型的交互过程是 connect、query、query ... query、quit。注意,所有的 SQL 语句类型,包括 DML、DDL,以及 multi-statement 都是用 query 命令处理的。

建立连接的过程在 obmp_connect,它执行用户认证鉴权,如果鉴权成功,会创建一个 ObSQLSession 对象(位于 src/sql/session)唯一表示一个数据库连接。所有其他命令处理都会访问这个 session 对象。

OceanBase 源码解读(三)SQL 的一生(下)-4

上图是 query 的处理类ObMPQuery,它是一条 SQL 一生的开始。process 方法是入口。

这里有个工具类 TraceId,在 mpquery 入口的地方会初始化它,在后续处理过程中它是一个线程局部变量,可以全局访问。它是一条 SQL 一次处理过程的一个唯一标识,如果执行过程中切换了线程,或者执行了 RPC,都会带上这个 ID。在 oblog 打印的所有调试日志中,都包含一个以 Y 开头的十六进制串(猜猜为什么以 Y 开头),就是 TraceId,它可以把不同位置打印的日志串起来。OceanBase 研发同学查问题时都习惯 grep 到 TraceId 相关的所有日志。

如果一条 SQL 串的格式是stmt;stmt;,即 multi-statement,这是 MySQL 协议的一种特殊优化,可以一次发送多条语句执行,并返回多个结果集(如果有)。这里的处理对于是否多语句有不同的处理。如果是一个单语句 autocommit=1 的单语句 DML 语句,因为要执行事务提交写日志,语句执行过后要等待写日志成功才能响应客户端。为了尽快让出线程资源,会挂起相关上下文,在日志提交成功之后执行回调。这里一些特殊的代码逻辑就是在处理这个优化。

因为要处理多语句的逻辑,首先通过 ObParser 的一个快速解析入口 split_multiple_stmt 把每条语句拆分出来,对每条语句进行 process_single_stmt。事务控制逻辑暂且忽略,最后 do_process 进入了 SQL 模块。

OceanBase 源码解读(三)SQL 的一生(下)-1

SQL 的模块划分非常清晰。总入口是 sql/ob_sql.h/cpp 的 ObSql 类。do_process 会调用这个类的 stmt_query 方法:输入 SQL 语句字符串,输出一个包含物理执行计划和元信息的 ResultSet。外层打开并迭代结果集,把每一行结果发送给客户端。所以,协议和执行计划处理本身是“流式”的,并不需要查询到全部结果才返回客户端。

下面说说这些 SQL 的子模块。parser 模块执行语法分析,把 SQL 字符串解析为一个 ParseNode 组成的抽象语法树。他的接口类是 ObParser 类。parser 是由 bison 和 flex 生成的 C 语言代码,OceanBase 的 C 语言代码位于这里。parser 有一种快速解析模式,目标不是产生语法树,而且把 SQL 字符串参数化,具体原理我们将在之后的系列博客中解释。

OceanBase 源码解读(三)SQL 的一生(下)-2

抽象语法树没有 SQL 语义,resolver 对它进行分析,结合数据字典元信息(OceanBase 的代码叫 schema 模块),赋予它 SQL 语义。大部分语义报错是这个阶段产生的。比如“表已经存在”,“主键长度超过限制”等。resolver 模块的接口类是 ObResolver,它的输出是 ObStmt。这个模块是面向对象设计的,每种语句类型有一个 Resolver 和一个 Stmt。他们按照语句类型,位于不同的子目录:dmlddltcl 等。

对于非 SELECT 和 DML 之外的语句,如大多数 DDL 语句解析到这里就可以执行了。这类简单语句类型统称为“命令”,由 engine/cmd 目录下的 executor 直接执行。DDL 是通过 rootservice(RS)执行的,所以其 executor 实际是发送 RPC。事务控制语句则在本机直接调用事务层。

对于 SELECT 和 DML 及带数据操作的 DDL,则需要产生执行计划。优化器(sql/optimizer)的接口类是 ObOptimizer,以上一步生成的 ObDMLStmt(含 SELECT)为输入,执行基于代价的优化,生成一个逻辑执行计划(ObLogPlan)。逻辑执行计划是由 OceanBase 的关系运算算子 ObLogicalOperator 组成的树状结构。改写(sql/rewrite)是优化器的一部分,执行等价的关系运算改写,产生潜在更好的执行计划候选,这里有一系列改写规则。改写规则的入口类是 ObTransformerImpl,输入输出都是 ObDMLStmt(当时正值变形金刚电影很火的时候)。

engine 目录下是 SQL 执行引擎,也叫做物理执行计划(ObPhysicalPlan),它是由物理算子(ObPhyOperator)组成的树状结构。执行过程是一种火山模型的流水线。

code_generator 模块负责把逻辑执行计划转换为能够高效执行的物理执行计划。它的接口类是 ObCodeGenerator。这里面比较复杂的是表达式的生成过程。

物理执行计划树在生成以后,在执行期间它的状态是静态的。同一个物理执行计划可以有多个并行执行。cg 产生的物理执行计划一般会被保存到计划缓存(即 sql/plan_cache 目录)中。前面讲到 parser 有一种特殊的快速解析模式,快速解析后的 SQL 会被尝试从计划缓存中直接捞可用的物理执行计划。如果没有合适的,才会执行上文所述的从 resolver 到 cg 的“硬解析”流程。

SQL 的一生很长,在这篇文章中,我们带大家粗粗走了个 “闭环”。如果在阅读过程中有任何问题,欢迎与我们交流。

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论