友情链接
星环科技分布式交易型数据库KunDB对Oracle语法各个方面高度兼容,尤其是能够完整支持PL/SQL,成功解决了Oracle业务迁移到国产化数据库的核心痛点。其中,发挥核心作用的是星环科技自主研发的PL/SQL编译器,其在支持复杂PL/SQL程序的编译和执行方面,展现出比传统解释执行更高的性能。
特别值得一提的是,KunDB原创的中间指令TIR,为数据库查询和程序提供了更深层次的优化可能性。TIR可以被转译成LLVM IR,从而借助于LLVM的优化能力,对用户编写的PL/SQL进行优化。除此之外,TIR还拥有一套异常处理机制,增强了程序的稳定性和可靠性。
本文将详细介绍KunDB SQL引擎的重要组成部分——PL/SQL编译器,并重点解析原创的中间指令TIR技术原理,同时通过2个具体的案例来介绍其实现过程和效果。
KunDB的PL/SQL编译器不仅能够将PL/SQL代码转化为计算机能够高效执行的“语言”,而且通过自研的中间指令TIR,实现了更高效的代码优化。整体上,KunDB的PL/SQL编译过程可以分为以下5步:
KunDB PL/SQL编译流程图
当KunDB收到一条PL/SQL时,首先由解析器模块负责对源代码进行词法分析与语法分析,生成抽象语法树(AST)。AST是一种结构化的表示形式,用于展示代码的结构和层次关系,类似于代码的“骨架“。
接下来,编译模块着手处理AST,为这幅代码的“骨架”添加额外信息,帮助计算机更高效地理解AST。首先,编译上下文控制模块会对PL/SQL代码的作用域进行严格控制,确保代码逻辑的准确性。然后,符号化模块执行一系列操作,如静态单一赋值(SSA)、符号命名、内部表示生成和外部符号注入,生成一个符号化的AST。简单来说,符号化的AST就像是一张详细的地图,不仅展示了代码结构,还标注了每个变量和函数的位置和作用范围,让代码在后续阶段能够被正确理解和执行。
在这之后,编译模块中的指令生成模块会分别处理PL指令和SQL指令。其中PL指令表示过程处理指令,如赋值、循环、条件语句,而SQL指令,顾名思义,就是指SQL语句。这两部分共同构成了PL/SQL。在中间指令生成这一步,PL与SQL指令会被转换为KunDB原创的中间指令TIR。
大家可能会问:为什么不直接将PL/SQL代码直接转化为计算机可执行的语言,而要经过中间指令?
实际上,KunDB 采用了 Multi-Pass 技术,即在处理源代码时分多次扫描和转换代码。这样做的好处是能够逐步细化和优化代码,以提升编译器的效率和生成代码的质量。通过多次转化为不同的中间指令,能够得到计算机可以高效执行的语言。KunDB将原创的中间指令TIR作为桥梁,连接 PL/SQL 高级语言特性与低层次 LLVM IR(LLVM中间指令) ,让代码拥有更多的优化空间。
在 TIR 生成后,KunDB 分别对PL指令与SQL指令进行转化与优化。PL类型指令被转化为 LLVM IR,借助LLVM的优化能力进行进一步优化。这些被优化的指令随后会被转化为一个完整的二进制结构,其中包含指令集以及符号表。SQL 指令则通过代理执行系统处理。对于cursor或一些特殊类型(如自定义类型)的SQL,表达式生成器会将它们转化为计算引擎可以理解的形式(如一元表达式、系统函数表达式等等)。其他SQL则会被 SQL Rewrite 回调至 SQL 引擎,并在SQL引擎中进行逻辑计划与物理计划的优化。
最后,所有这些处理后的代码会被存储到元数据中,并由运行时模块负责执行。这样,我们最终得到了计算机能够高效执行的“语言”。
大家可能对KunDB原创的中间指令TIR感到好奇,想知道更多细节。接下来详细介绍下TIR的设计理念、优势以及生成过程。
TIR(Transwarp Intermediate Representation)的设计初衷是为了更好地支持 PL/SQL 中的各种过程式语义和 SQL 的多种用法。具体来说,TIR 能够处理过程式语义,包括循环、条件语句、跳转、赋值和函数调用等。同时,TIR 也能处理 SQL 语义,涵盖显式游标、隐式游标、游标变量和动态 SQL。这些特性使得 TIR 可以高效地处理过程式编程和 SQL 查询操作,提供了更大的灵活性和功能性。
TIR的设计充分考虑了代码的可优化性。首先,TIR支持Multi-Pass编译,这意味着代码可以被多次扫描和优化。在这个过程中,TIR代码会被转译为LLVM IR,然后利用LLVM的强大优化能力对代码进行深度优化。此外,TIR不仅能够生成LLVM IR,还可以进一步编译为原生机器代码进行执行。这种设计充分利用了LLVM现有的优化Pass,确保代码在不同平台上都能高效运行。通过生成原生机器代码,TIR能够最大化利用硬件资源,从而显著提升整体系统性能。
TIR具备卓越的平台扩展性。其代码生成支持多平台,IR存储后可以根据不同平台进行即时编译(JIT),实现一次编译,处处执行。这种跨平台的能力使得TIR在不同环境下都能有效运行,增强了系统的适应性和灵活性。无论是在不同的硬件架构还是操作系统上,TIR都能保证高效的执行性能。
TIR的设计中包含了对异常处理的完善支持。TIR支持异常的定义、绑定和捕捉,以及异常的冒泡和作用域管理,这对于复杂的数据库操作和流程控制尤为重要。通过这些设计,TIR能够更好地应对各种异常情况,保证系统的稳定性和可靠性。
TIR在设计上强调功能扩展的解耦合。各功能模块是独立的,添加新的功能特性时不会互相干扰。例如,添加新的方言支持时,只需实现相应的语法支持,而无需在运行时重新定义适配。这种解耦合设计使得TIR在功能扩展上更加灵活和高效。通过独立的模块化设计,TIR能够快速适应不断变化的需求和技术,提供持久的扩展能力。
接下来,用两个简单的例子,看看KunDB的PL/SQ编译器是如何将源代码一步步转化为TIR的。
以下是一个简单的PL/SQL函数,其中包含一些PL语句与SQL语句:
该函数定义了一个名为 “a”的变量,然后在一个 FOR 循环中将“a”的值逐次增加 5,最后将“a”插入“example_table”中,并返回 “a”的值。
下面结合流程图详细介绍一下这个示例函数是如何被一步步地转化为TIR的。
源代码至TIR转化流程图
首先,PL/SQL 代码会经过词法分析和语法分析,生成一棵抽象语法树(AST)(见下图,有删减)。
PL/SQL案例AST图
这棵抽象语法树最顶端有一个根节点,代表这个示例函数本身。这个函数被分为四个部分,分别对应代码中的不同部分。
一些细心的读者可能会发现,有些语句中包含着其他语句。比如循环语句中还包含着赋值语句“a := a + 5”。实际上,每一个语句都能够被进一步拆分出更多的子节点,直到无法拆分为止。
随后,PL/SQL编译器会初始化上下文,把上下文理解为一个独立的代码运行环境。这个运行环境中会记录很多代码运行需要的信息。例如在这个例子中,函数定义了变量a:
于是,编译器就会在上下文的符号表中记录下“a”,当计算机在运行这段代码时再遇到“a”,就能够认识“a”是一个变量,并且能够在符号表中查询到“a”的类型与值。
随后,PL/SQL编译器就会从左到右地遍历抽象语法树中的一个个子节点,并且按顺序进行处理:
第二个案例是源代码被转化为TIR过程中的异常是如何处理的。以下是一个带异常的简单PL/SQL代码块:
这段代码声明了一个变量 v1 和一个异常 excp_1。在代码运行时,故意触发 excp_1 异常,然后捕获这个异常,并将 v1 设置为 1。当把这段代码翻译成 TIR 代码时,可分为三个部分(为便于阅读,有删改):
代码是从第0行开始运行的,但因为一开始放置的是错误处理部分,因此这里使用跳转语句跳转到第8行,到代码实际的开始位置运行。
当代码运行到第9行时,“Raise Exception xxx”代表引发编号为xxx的异常。
这里异常被触发,所以会跳转到异常处理部分。那么程序如何知道异常处理部分的地址呢?实际上在生成时,每一条指令都会被添加异常处理地址(HIP),指向出现异常后跳转的异常处理器地址,而对于这条语句而言,其异常处理器的地址为第1行,因此会跳转到第1行继续运行。
在第1行,会进行一次条件跳转。如果当前异常代码为 99001,则跳到第 2 行,否则跳到第 4 行。
如果跳转到第2行,那么会在这里完成异常处理,KunDB在PL/SQL中自己定义的异常处理是将“v1”变量赋值为1,因此这里进行了赋值操作。在操作完成后,会跳转到第5行。
如果跳转到第4行,则说明当前的代码中没有合适的处理器能够处理该编号的异常,因此会跳转到-2位置,转交上级异常处理器处理。
在跳转到第5行后,会重置SQLCODE与SQLERRM,这两个变量是用来记录异常代码和错误信息的。这两行会确保异常处理完毕后不会影响后续操作。
最后“Jump 33”这行指令跳到当前代码块的结尾,结束这段程序。
本文为大家讲解了KunDB的PL/SQL编译器的完整编译过程,详细介绍了KunDB原创的中间指令 TIR 的设计和优势,并结合案例讲述了PL/SQL编译器对于PL语句、SQL语句以及异常处理的不同处理方法,希望能够让大家对于KunDB的PL/SQL编译器以及其中TIR的设计与生成有较为深入的理解。
友情链接
星环科技分布式交易型数据库KunDB对Oracle语法各个方面高度兼容,尤其是能够完整支持PL/SQL,成功解决了Oracle业务迁移到国产化数据库的核心痛点。其中,发挥核心作用的是星环科技自主研发的PL/SQL编译器,其在支持复杂PL/SQL程序的编译和执行方面,展现出比传统解释执行更高的性能。
特别值得一提的是,KunDB原创的中间指令TIR,为数据库查询和程序提供了更深层次的优化可能性。TIR可以被转译成LLVM IR,从而借助于LLVM的优化能力,对用户编写的PL/SQL进行优化。除此之外,TIR还拥有一套异常处理机制,增强了程序的稳定性和可靠性。
本文将详细介绍KunDB SQL引擎的重要组成部分——PL/SQL编译器,并重点解析原创的中间指令TIR技术原理,同时通过2个具体的案例来介绍其实现过程和效果。
KunDB的PL/SQL编译器不仅能够将PL/SQL代码转化为计算机能够高效执行的“语言”,而且通过自研的中间指令TIR,实现了更高效的代码优化。整体上,KunDB的PL/SQL编译过程可以分为以下5步:
KunDB PL/SQL编译流程图
当KunDB收到一条PL/SQL时,首先由解析器模块负责对源代码进行词法分析与语法分析,生成抽象语法树(AST)。AST是一种结构化的表示形式,用于展示代码的结构和层次关系,类似于代码的“骨架“。
接下来,编译模块着手处理AST,为这幅代码的“骨架”添加额外信息,帮助计算机更高效地理解AST。首先,编译上下文控制模块会对PL/SQL代码的作用域进行严格控制,确保代码逻辑的准确性。然后,符号化模块执行一系列操作,如静态单一赋值(SSA)、符号命名、内部表示生成和外部符号注入,生成一个符号化的AST。简单来说,符号化的AST就像是一张详细的地图,不仅展示了代码结构,还标注了每个变量和函数的位置和作用范围,让代码在后续阶段能够被正确理解和执行。
在这之后,编译模块中的指令生成模块会分别处理PL指令和SQL指令。其中PL指令表示过程处理指令,如赋值、循环、条件语句,而SQL指令,顾名思义,就是指SQL语句。这两部分共同构成了PL/SQL。在中间指令生成这一步,PL与SQL指令会被转换为KunDB原创的中间指令TIR。
大家可能会问:为什么不直接将PL/SQL代码直接转化为计算机可执行的语言,而要经过中间指令?
实际上,KunDB 采用了 Multi-Pass 技术,即在处理源代码时分多次扫描和转换代码。这样做的好处是能够逐步细化和优化代码,以提升编译器的效率和生成代码的质量。通过多次转化为不同的中间指令,能够得到计算机可以高效执行的语言。KunDB将原创的中间指令TIR作为桥梁,连接 PL/SQL 高级语言特性与低层次 LLVM IR(LLVM中间指令) ,让代码拥有更多的优化空间。
在 TIR 生成后,KunDB 分别对PL指令与SQL指令进行转化与优化。PL类型指令被转化为 LLVM IR,借助LLVM的优化能力进行进一步优化。这些被优化的指令随后会被转化为一个完整的二进制结构,其中包含指令集以及符号表。SQL 指令则通过代理执行系统处理。对于cursor或一些特殊类型(如自定义类型)的SQL,表达式生成器会将它们转化为计算引擎可以理解的形式(如一元表达式、系统函数表达式等等)。其他SQL则会被 SQL Rewrite 回调至 SQL 引擎,并在SQL引擎中进行逻辑计划与物理计划的优化。
最后,所有这些处理后的代码会被存储到元数据中,并由运行时模块负责执行。这样,我们最终得到了计算机能够高效执行的“语言”。
大家可能对KunDB原创的中间指令TIR感到好奇,想知道更多细节。接下来详细介绍下TIR的设计理念、优势以及生成过程。
TIR(Transwarp Intermediate Representation)的设计初衷是为了更好地支持 PL/SQL 中的各种过程式语义和 SQL 的多种用法。具体来说,TIR 能够处理过程式语义,包括循环、条件语句、跳转、赋值和函数调用等。同时,TIR 也能处理 SQL 语义,涵盖显式游标、隐式游标、游标变量和动态 SQL。这些特性使得 TIR 可以高效地处理过程式编程和 SQL 查询操作,提供了更大的灵活性和功能性。
TIR的设计充分考虑了代码的可优化性。首先,TIR支持Multi-Pass编译,这意味着代码可以被多次扫描和优化。在这个过程中,TIR代码会被转译为LLVM IR,然后利用LLVM的强大优化能力对代码进行深度优化。此外,TIR不仅能够生成LLVM IR,还可以进一步编译为原生机器代码进行执行。这种设计充分利用了LLVM现有的优化Pass,确保代码在不同平台上都能高效运行。通过生成原生机器代码,TIR能够最大化利用硬件资源,从而显著提升整体系统性能。
TIR具备卓越的平台扩展性。其代码生成支持多平台,IR存储后可以根据不同平台进行即时编译(JIT),实现一次编译,处处执行。这种跨平台的能力使得TIR在不同环境下都能有效运行,增强了系统的适应性和灵活性。无论是在不同的硬件架构还是操作系统上,TIR都能保证高效的执行性能。
TIR的设计中包含了对异常处理的完善支持。TIR支持异常的定义、绑定和捕捉,以及异常的冒泡和作用域管理,这对于复杂的数据库操作和流程控制尤为重要。通过这些设计,TIR能够更好地应对各种异常情况,保证系统的稳定性和可靠性。
TIR在设计上强调功能扩展的解耦合。各功能模块是独立的,添加新的功能特性时不会互相干扰。例如,添加新的方言支持时,只需实现相应的语法支持,而无需在运行时重新定义适配。这种解耦合设计使得TIR在功能扩展上更加灵活和高效。通过独立的模块化设计,TIR能够快速适应不断变化的需求和技术,提供持久的扩展能力。
接下来,用两个简单的例子,看看KunDB的PL/SQ编译器是如何将源代码一步步转化为TIR的。
以下是一个简单的PL/SQL函数,其中包含一些PL语句与SQL语句:
该函数定义了一个名为 “a”的变量,然后在一个 FOR 循环中将“a”的值逐次增加 5,最后将“a”插入“example_table”中,并返回 “a”的值。
下面结合流程图详细介绍一下这个示例函数是如何被一步步地转化为TIR的。
源代码至TIR转化流程图
首先,PL/SQL 代码会经过词法分析和语法分析,生成一棵抽象语法树(AST)(见下图,有删减)。
PL/SQL案例AST图
这棵抽象语法树最顶端有一个根节点,代表这个示例函数本身。这个函数被分为四个部分,分别对应代码中的不同部分。
一些细心的读者可能会发现,有些语句中包含着其他语句。比如循环语句中还包含着赋值语句“a := a + 5”。实际上,每一个语句都能够被进一步拆分出更多的子节点,直到无法拆分为止。
随后,PL/SQL编译器会初始化上下文,把上下文理解为一个独立的代码运行环境。这个运行环境中会记录很多代码运行需要的信息。例如在这个例子中,函数定义了变量a:
于是,编译器就会在上下文的符号表中记录下“a”,当计算机在运行这段代码时再遇到“a”,就能够认识“a”是一个变量,并且能够在符号表中查询到“a”的类型与值。
随后,PL/SQL编译器就会从左到右地遍历抽象语法树中的一个个子节点,并且按顺序进行处理:
第二个案例是源代码被转化为TIR过程中的异常是如何处理的。以下是一个带异常的简单PL/SQL代码块:
这段代码声明了一个变量 v1 和一个异常 excp_1。在代码运行时,故意触发 excp_1 异常,然后捕获这个异常,并将 v1 设置为 1。当把这段代码翻译成 TIR 代码时,可分为三个部分(为便于阅读,有删改):
代码是从第0行开始运行的,但因为一开始放置的是错误处理部分,因此这里使用跳转语句跳转到第8行,到代码实际的开始位置运行。
当代码运行到第9行时,“Raise Exception xxx”代表引发编号为xxx的异常。
这里异常被触发,所以会跳转到异常处理部分。那么程序如何知道异常处理部分的地址呢?实际上在生成时,每一条指令都会被添加异常处理地址(HIP),指向出现异常后跳转的异常处理器地址,而对于这条语句而言,其异常处理器的地址为第1行,因此会跳转到第1行继续运行。
在第1行,会进行一次条件跳转。如果当前异常代码为 99001,则跳到第 2 行,否则跳到第 4 行。
如果跳转到第2行,那么会在这里完成异常处理,KunDB在PL/SQL中自己定义的异常处理是将“v1”变量赋值为1,因此这里进行了赋值操作。在操作完成后,会跳转到第5行。
如果跳转到第4行,则说明当前的代码中没有合适的处理器能够处理该编号的异常,因此会跳转到-2位置,转交上级异常处理器处理。
在跳转到第5行后,会重置SQLCODE与SQLERRM,这两个变量是用来记录异常代码和错误信息的。这两行会确保异常处理完毕后不会影响后续操作。
最后“Jump 33”这行指令跳到当前代码块的结尾,结束这段程序。
本文为大家讲解了KunDB的PL/SQL编译器的完整编译过程,详细介绍了KunDB原创的中间指令 TIR 的设计和优势,并结合案例讲述了PL/SQL编译器对于PL语句、SQL语句以及异常处理的不同处理方法,希望能够让大家对于KunDB的PL/SQL编译器以及其中TIR的设计与生成有较为深入的理解。