PL语言(Procedural Language) 是一种程序语言, 又称过程化结构查询语言,它是一种建立在普通SQL语言之上的编程语言。
讨论PL语言是一个有意思的话题,因为软件行业被大数据、组件化的思想浪潮捶打过多年,所以多数从业人员一旦提及PL语言,都会在心里冒出一句疑问,“那不是已经过时的语言?”
那为什么YashanDB还要选择开发PL语言?正所谓仁者乐山,智者乐水,笔者的观点是:在技术领域,没有绝对的对错之分,关键在于是否合适。物竞天择,在软件行业,最合适应用业务实现的技术会用行动来投票决定。
如电信、证券、银行等传统行业,强调业务的高并发和高可用,通过PL语言来实现业务应用逻辑是主流选择。而在互联网行业,选择PL语言来实现业务逻辑的几乎绝迹,替代的是,业务广泛通过各种CRUD技术中间件 + JDBC,将业务的逻辑实现转移到数据库之外。
PL语言之所以成为很多业务的选择,在于其独特的特性价值:
因此笔者认为除非发生巨大的技术变更,PL语言特性将在相当长的一段时间内会持续保持很强的竞争力。
在之前一篇论述Oracle兼容性的文章中,笔者以整个数据库视角来论述过国产数据库在这个方面应该达到的4个标准。(可点击此处查看文章:《论Oracle兼容性,我们需要做什么?》)当这个范围缩小到PL语言时,Oracle兼容性的视角该如何呈现呢?

Oracle兼容性是目前国产数据库最主要的一个工作任务,这个任务直接决定了数据库在做商业迁移时的成本,是商业竞争力的一个重要指标。类比于SQL语言,在PL语言视角上也做了四层划分。
Oracle兼容性不是一个简单的模仿行为,而是一个非常复杂和工程量庞大的逆向工程。目前国产数据库应该秉持务实的态度,专注于核心功能的实现和完善,同时确保高度的兼容和已实现功能的稳定性。
YashanDB作为一款全自研的数据库,从Oracle兼容性角度上技术具备一定优势。经过几年不断的打磨,在PL语言特性上已初露头角。目前已推出的YashanDB数据库版本PL特性具备以下优点:
以下将围绕上述优点,展开描述YashanDB PL语言实现范围。

上图为YashanDB PL语言特性实现架构。图示可见,PL引擎与SQL引擎在层次上是完全解耦的,通过SQL引擎绑定参数特性完成SQL语句的编译和执行,SQL产生结果集通过sender接口输出。通过缓存机制,完成PL和SQL编译体的缓存,便于使用时可以快速执行。在此架构下,PL语言是可以完全发挥SQL引擎支持的所有能力。
PL语言与结构化查询语言SQL的结合非常紧密,具体表现在以下方面:

SQL语言是一种数据描述语言,PL语言则是极大的扩展了数据库的可编程逻辑。PL语言源于元老级的Ada语言,SQL/PSM为业界标准,Oracle的PL/SQL语法形式为当前事实标准。可以实现面向过程、面向对象两种编程形式。
PL语言通过可编程逻辑和SQL集成,可以带来以下明显的好处:
使用PL语言程序很大阻力在难以调试PL语言程序。YashanDB支持断点、STEP INTO、STEP OUT、变量查看、调用栈查看、源码查看等功能。极大方便用户进行PL语言的跟踪定位。
与业界常见的调试器实现方案不同,YashanDB实现了一个轻量级的DEBUGGER,不需要组织不同的调试语句,不存在查看会话看板,不需要去ATTACH操作;一键启动调试,使用复杂度大大降低。
YashanDB PL语言的调试器特性,近期即将发布。

笔者从基于PL语言开发者的角度,给出若干条建议如下:
以下根据提供建议,进行逐个展开。
如下表,笔者给出PL语言对象推荐使用场景和高效使用建议,提供大家参考。

这个章节,笔者将通过举1个游标特性相关例子给大家一个直观的感受:
sql 代码解读复制代码DECLARE
cursor cur1 is select column1 c1 from table1;
result cur1%rowtype;
BEGIN
open cur1;
loop
fetch cur1 into result;
exit when cur1%notfound;
process(result);
end loop;
close cur1;
END;
/其实通过例子的分析,这个特性是完成游标的遍历,所以实际上选择FOR语句,可以更为简洁的完成相应功能。改写后例子如下:
sql 代码解读复制代码BEGIN
for result in (select column1 c1 from table1) then
process(result);
end for;
END;
/是不是整体简洁度高了很多?实际在PL语言中提供了很多逻辑行语句,语句间并不存在好坏,需要从业务逻辑角度选择合适的语句去实现。
PL语言中常见的使用SQL的方式,有静态SQL特性、游标、动态SQL等。常见的业务逻辑是通过SQL获取数据后,需要进一步加工处理,然后返回处理后结果。
建议优先选择静态SQL特性,有以下原因:

其次推荐使用游标特性。游标是非常灵活的查询方式,而且存在多种游标形态,常用的为显式游标和系统游标。但相对于其他SQL查询特性,游标是需要变量形式承载,额外占用变量资源,同时遵循PL语言中变量的栈生命周期管理。
最后再是使用动态SQL特性。动态SQL适用于资源动态生成、动态拼接SQL语句和执行DDL语句,PL编译阶段难以检测的,需要到执行阶段进行编译执行,灵活度高但执行效率较低。
以上是三种常见的SQL查询相关PL特性比较,当然PL特性没有绝对的选择好坏,只有合适业务逻辑实现的才是最好的。
合理规划的函数调用,可以减少编译复杂度。如下举例,给了一个较为复杂的嵌套调用,从调用关系上形成了一个有向环图。
sql 代码解读复制代码CREATE FUNCTION F1 RETURN INT IS
result INT := 1;
BEGIN
result := F2();
result := result + F3();
result := result + F1();
return result;
END;
/
CREATE FUNCTION F2 RETURN INT IS
result INT;
BEGIN
result := result + F4();
return result;
END;
/
CREATE FUNCTION F3 RETURN INT IS
result INT;
BEGIN
result := result + F2();
result := result + F4();
return result;
END;
/
CREATE FUNCTION F4 RETURN INT IS
result INT;
BEGIN
result := result + F1();
return result;
END;
/一个有向环图按级联编译方式,通过深度遍历优先方式进行编译时,会优先按调用链寻找叶子节点,级联展开编译。
如果深度过深,会使得编译链过深,占用大量编译资源。如图所示,我们在进行级联编译时,针对递归、嵌套、相同编译流程多次重复调用的函数等各种情形,会进行检测并及时剪枝。

当然笔者认为函数调用不可避免会出现递归和嵌套调用的情形出现,所以选择如何在合适的时机选用递归和嵌套调用,这是编程关键。但不可以滥用,必须有合适的退出条件,避免对资源产生极大损耗。
如果有一个数据库对象的编译使用了另一个对象的元数据信息,两个对象间就存在了依赖关系。如果被依赖对象发生了元数据信息一旦发生变更(元数据变更发生,即产生了DDL操作),依赖对象编译信息就失效了。
因为PL对象常见的实现逻辑,是封装大量的SQL调用,PL对象调用等,所以一个PL对象会产生大量的依赖对象。当依赖对象发生DDL,比如一个表动态增删了列,那么依据这个表的查询绑定的游标,其继承属性可能就会发生变化。再举一个例子,比如实现了一个自定义公共的字符串替换函数,当这个函数的实现发生变更,那么所有依赖这个公共函数的PL对象、SQL语句等都应该发生失效重编译的动作,否则原编译结构中包含的实现逻辑就是错误的。
在PL对象实现时,会根据PL对象的依赖关系构造依赖链。如果一个对象发生元数据变更,那么这个依赖链上所有的对象都会被病毒似的传染失效。如果有大量的对象失效,那么在调用时可能会产生大量的重编译动作,极端情况会导致资源的突发损耗,甚至耗尽报错,性能也会产生极大的波动。
所以建议一个PL对象的依赖对象适当要控制规模,而且通过预先执行DDL方式,确保缓存中编译体有效。如果必须有DDL操作,那么建议在DDL操作完成后,通过ALTER RECOMPILE的命令,将PL对象提前编译为有效状态。
此外在YashanDB PL语言实现过程中,我们发现这种问题,也及时做出了一些应对措施,比如通过松耦合操作,及时剪断病毒式的传染。
尽管如此,PL语言仍存在部分不足:
如何有计划、有节奏地实现Oracle的PL语言特性,并能进一步克服PL语言的缺点,这是国产数据库在PL语言特性上面对的主要问题。比如第一点,PL语言是缺乏其他高级语言的UT测试框架、静态检查工具、内存工具等各种开发者生态工具,此外覆盖率报告、内存泄露检查等完全缺失。
作者:崖山数据库系统YashanDB 链接:https://juejin.cn/post/7389912518704332812 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。