目 录CONTENT

文章目录

【GaussDB】507内核新特性-PLSQL字节码(三)

DarkAthena
2026-07-19 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

【GaussDB】507内核新特性-PLSQL字节码(三)

上一篇《【GaussDB】507内核新特性-PLSQL字节码(二)》在结尾提到,官方文档和日志描述都不太完善,二进制里grep出来的字节码日志有很多没有命中,成了个遗憾。本文根据日志描述反向倒推测试场景,尝试命中更多上一篇没有覆盖到的BC日志码。

测试环境与上一篇一致:GaussDB 507.0.0 (build 19fa72ae),A兼容模式,plsql_code_type=BYTECODE

由于没有gsql客户端,本次测试使用Java + gaussdbjdbc.jar连接数据库,通过JDBC的SQLWarning链捕获client_min_messages=log输出的LOG消息,效果与gsql终端输出一致。

// 核心思路:JDBC的SQLWarning可以捕获server端LOG消息
Statement st = conn.createStatement();
st.execute("set plsql_code_type=BYTECODE");
st.execute("set client_min_messages=log");
st.execute("set logging_module='on(BYTECODE)'");
st.execute("call some_proc()");
SQLWarning w = st.getWarnings();
while (w != null) {
    System.out.println(w.getMessage());
    w = w.getNextWarning();
}

BC053: bytecode only support select statement.

上一篇只在TRIGGER场景命中过BC053,实际上只要是存储过程里直接写DML语句(INSERT/UPDATE/DELETE/MERGE),都会触发这个日志。

create or replace procedure p_bc_insert is
begin
  insert into dml_test values(1,1);
end;
/
LOG:  [ByteCode] BC053 bytecode only support select statement.
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33663.
LOG:  [ByteCode] Function: p_bc_insert(), InterpreterBytecode:
[0] exec_stmt_begin 
[21] exec_stmt_treebased  
[34] exec_stmt_end 
[47] exec_stmt_begin 
[68] exec_stmt_end 
[81] return0 5 

LOG:  Start ByteCode!

UPDATE、DELETE、MERGE同理,都输出BC053,但存储过程本身仍然支持字节码,只是DML语句编译为exec_stmt_treebased指令。exec_stmt_treebased是字节码指令集中的一个正常指令,字节码引擎执行到该指令时会调用AST引擎处理这一条语句,然后继续执行下一条字节码指令。也就是说,存储过程里只要有DML,那部分就是树引擎跑的,其余部分还是字节码。

FORALL语句也是如此,不管FORALL里是UPDATE还是INSERT还是DELETE,都会输出BC053:

create or replace procedure p3_forall_upd is
type num_arr is varray(10) of int;
arr num_arr := num_arr(1,2,3);
begin
forall i in 1..arr.count
update forall_test3 set val = val + 100 where id = arr(i);
end;
/
LOG:  [ByteCode] BC053 bytecode only support select statement.
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33724.

FORALL本身是支持字节码的(fori_prepare + fori),只是循环体里的DML语句编译为exec_stmt_treebased。但FORALL带SAVE EXCEPTIONS则会直接导致整个存储过程不支持字节码(BC027 + BC030 + BC039),上一篇已测过。

BC054: Simple expr check occurs exeception. Failed to eval simple expr.

这个日志在异常处理块里被触发了,具体场景是过程体里有一个会在编译期求值失败的表达式:

create or replace procedure p_bc_except is
v int;
begin
  v := 1/0;
exception
when others then
  v := 0;
end;
/
LOG:  [ByteCode] BC054 Simple expr check occurs exeception. Failed to eval simple expr.
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33651.
LOG:  [ByteCode] Function: p_bc_except(), InterpreterBytecode:
[0] exec_stmt_begin 
[21] exec_block_init 
[22] exec_block_begin 
[27] exec_stmt_begin 
[48] exec_stmt_treebased  
[61] exec_stmt_end 
[74] exec_block_end 
...
LOG:  Start ByteCode!

1/0在简单表达式检查时抛出了除零异常,导致该语句编译为exec_stmt_treebased指令,但整个存储过程仍支持字节码。BC054的含义应该是:字节码引擎在尝试将表达式编译为字节码指令时,如果简单表达式求值检查失败(比如常量折叠时除零),就会将该语句改为exec_stmt_treebased,交给树引擎执行。

BC055 + BC072: 带出参函数在简单表达式中调用

当存储过程里通过SELECT语句调用一个带OUT参数的函数时,会触发BC055:

create or replace function f2_bc061(o out int) return int is
begin
  o := 1;
  return 2;
end;
/

create or replace procedure p2_bc061 is
v int;
o int;
begin
  select f2_bc061(o) into v;
end;
/
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33675.
LOG:  [ByteCode] Function: p2_bc061(), InterpreterBytecode:
[0] exec_stmt_begin 
[21] expression expr: select f2_bc061(o) 
[58] storen_sql_attributes 
[59] storen 9 
[64] exec_stmt_end 
...
LOG:  Start ByteCode!
LOG:  [ByteCode] BC055 function(33674) with outparam called in simple expr does not support.
LOG:  [ByteCode] BC072 Expression: select f2_bc061(o) is degenerated.
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33674.

BC055表示带出参的函数在简单表达式中调用时不支持字节码,BC072表示该表达式被"退化"(degenerated)。调用方存储过程仍然支持字节码,被调用的带出参函数本身也支持字节码,只是调用点这个表达式编译为exec_stmt_treebased

BC061: 带出参函数在PERFORM语句中使用

create or replace procedure p2_bc064 is
o int;
begin
  perform f2_bc064(o);
end;
/
LOG:  [ByteCode] BC061 not spport func with out param used in execsql statement, sql is :perform f2_bc064(o);
LOG:  [ByteCode] BC040 This function supports bytecode, func_oid: 33679.

BC061表示在execsql语句(PERFORM也是一种execsql)中使用了带出参的函数,不支持字节码,但存储过程本身仍支持。注意这里BC061的日志文本里有个拼写错误:"spport"应该是"support"。

BC074: behavior_compat_flags 检查

这个日志在关闭proc_outparam_override后创建带出参的函数时触发:

-- 先关闭 proc_outparam_override
set behavior_compat_options='...(不包含proc_outparam_override)...';

create or replace function f3_bc056(o out int) return int is
begin
  o := 1;
  return 2;
end;
/

declare o int; v int; begin v:=f3_bc056(o); end;
LOG:  [ByteCode] BC074 This function can not supports bytecode, func_oid: 33704.Please check the GUC parameter behavior_compat_flags.
select bytecode from dump_plsql_bytecode('f3_bc056'::regproc);
  Not supported

BC074直接导致函数不支持字节码。这个日志对应文档约束中"该特性仅在兼容性参数behavior_compat_options设置中包含...proc_outparam_override...参数选项时,支持执行字节码"。实际测试发现,不是某个单独的参数控制,而是只要proc_outparam_override不在behavior_compat_options里,带出参的函数就会触发BC074。普通存储过程(不带出参)在关闭proc_outparam_override时仍然支持字节码。

BC030: GOTO语句不支持

GOTO语句会导致整个存储过程不支持字节码:

create or replace procedure p3_goto_valid is
v int := 0;
begin
  v := 1;
  goto skip;
  v := 2;
  <<skip>>
  v := 3;
end;
/
LOG:  [ByteCode] BC030 Stmt GOTO does not support with some error, lineno: 5.
LOG:  [ByteCode] BC039 This function doesn't support bytecode, func_oid: 33720.

不管GOTO的标签是否有效,只要存储过程里用了GOTO,整个存储过程就不支持字节码。而EXIT和CONTINUE(带标签或不带标签)都是支持字节码的。

BC068: subtype的新触发场景

上一篇测BC068时,只在嵌套子程序场景命中过。这次发现subtype变量也能触发BC068,而且会直接导致整个存储过程不支持字节码。

场景1:subtype定义在同一个package中

create or replace package pkg_bc036b is
subtype myint is int;
procedure p;
end;
/

create or replace package body pkg_bc036b is
procedure p is
v myint;
begin
  v := 1;
  v := v + 1;
  raise notice '%', v;
end;
end;
/
LOG:  [ByteCode] BC068 bytecode neither support nest sub procedure/function nor subtype.
select bytecode from dump_plsql_bytecode('pkg_bc036b.p'::regproc);
  Not supported

场景2:subtype定义在匿名块中

create or replace procedure p_bc_subtype2 is
begin
  declare
    subtype myint is int;
    v myint;
  begin
    v := 1;
  end;
end;
/
LOG:  [ByteCode] BC068 bytecode neither support nest sub procedure/function nor subtype.
select bytecode from dump_plsql_bytecode('p_bc_subtype2'::regproc);
  Not supported

但在上一篇中测试的跨package引用subtype(v pkg5.varchar8),却只触发了BC036且仍然支持字节码。这说明subtype的不支持行为存在不一致:同package内定义的subtype或匿名块内的subtype会导致BC068(整体不支持),而跨package引用subtype只触发BC036(局部不支持,整体仍支持)。考虑到BC068的日志文本是"bytecode neither support nest sub procedure/function nor subtype",subtype和嵌套子程序共用同一个检查逻辑,这恐怕是个实现上的简化处理。

确认支持字节码的场景

以下场景在测试中确认支持字节码(输出BC040 + 正常字节码),其中部分语句编译为exec_stmt_treebased(由AST引擎执行该条语句),但存储过程整体仍走字节码框架:

场景字节码情况备注
ARRAY构造 array[1,2,3]支持storen_array
ROW构造 row(1,2)支持eval_rowexpr + store_row
子查询赋值 v := (select 1)treebased但整体支持
IS NULL表达式支持nulltest
CASE WHEN表达式支持常量折叠
COALESCE支持常量折叠
NULLIF支持nullif
GREATEST/LEAST支持max/min
XML类型操作支持xml XMLPARSE
数组元素赋值 v(1) := 10支持store_array_elem
index-by表元素赋值支持store_tableofindex_elem
varray元素赋值支持同上
GET DIAGNOSTICS支持exec_stmt_getdiag
COMMIT/ROLLBACKtreebasedexec_stmt_treebased_transaction
EXECUTE IMMEDIATEtreebased动态SQL
异常处理 EXCEPTION支持exec_exception_begin/end
嵌套匿名块支持exec_block_init/begin/end
嵌套RECORD类型支持
cursor FOR循环支持forc_prepare + forc
WHILE循环支持loopwhile
FOR IN REVERSE循环支持fori_prepare_reverse + fori_reverse
CONTINUE语句支持
EXIT/EXIT label支持
cursor属性 c%rowcount支持loadn_cursor_attributes
函数内调用其他函数支持invokemethod_definer
带出参函数直接赋值 v := f(o)支持procedure expression
REFCURSOR出参支持loadn_return_cursor
varray出参支持
复合类型出参支持
表类型出参支持

无法触发的BC日志码

以下BC日志码在本次测试中未能通过SQL语句触发,原因各不相同:

BC码原因分析
BC001只有非A兼容模式才会触发,当前环境为A模式
BC002package初始化匿名块本身不作为字节码对象,内部procedure仍支持
BC008无法通过正常DDL创建含"无效参数类型"的函数
BC016processStatement异常,内部错误
BC017/BC018内部错误,找不到proc或language tuple
BC020所有测试的表达式类型均支持字节码
BC022$1参数在字节码中被正常处理为loadn 0
BC023访问null record的字段正常支持
BC024/BC025无法构造出"incorrect type"或"invalid op"的场景
BC028返回复合类型/refcursor均支持
BC029所有测试的语句类型均支持或treebased
BC031无权在pg_catalog创建对象
BC032表类型/复合类型出参均支持
BC033GET DIAGNOSTICS对record变量字段赋值正常支持
BC034RESULT_OID诊断类型正常支持
BC037tableof类型转text正常支持
BC038bytea类型转换正常支持
BC041/BC042/BC044数组下标访问正常支持
BC043无效字段名在运行时报错,不触发BC日志
BC045XML类型操作正常支持
BC046集合类型操作正常支持
BC047/BC049cursor/record赋值均正常支持
BC048无法创建含无效typeoid的对象
BC052子查询作为表达式编译为exec_stmt_treebased,但非BC052
BC056关闭proc_outparam_override时触发的是BC074而非BC056
BC058函数无返回值时运行报错,dump显示Not supported但无BC日志
BC060procedure带RETURN值时编译报错,无法执行
BC064调用方不提供out参数时函数找不到
BC065出参类型转换正常支持
BC067系统函数调用时被当作普通函数调用,不影响调用方
BC071/BC073表达式类型变更/复杂化未触发
BC075变量数不匹配的EXECUTE INTO正常treebased
BC076processExpr异常,内部错误
BC078栈大小异常,内部错误
BC079权限回收后函数仍显示支持字节码
exit/continue label not match标签不匹配的procedure编译为invalid
outparam only supports var & row typerefcursor/varray出参均正常支持

其中BC056和BC079值得说明。BC056的日志文本是"bytecode doesn't support function with outparam when proc_outparam_override is off",但实际关闭proc_outparam_override后触发的是BC074("Please check the GUC parameter behavior_compat_flags"),可能是BC056在代码中被BC074覆盖了,或者BC056在更早的版本中使用,507版本改为BC074。BC079的日志文本是"func doesn't support bytecode because of permission error",但revoke execute权限后,函数仍然在第一次执行时生成了字节码(因为函数owner和执行者是同一用户,revoke不影响owner的权限)。

更新后的BC日志码汇总表

在上一篇表格基础上,补充本次新命中的BC日志码:

日志是否约束对应文档是否有命中日志自测是否支持字节码备注
BC053 bytecode only support select statement.YY(本次新增)Y(局部treebased)DML语句(INSERT/UPDATE/DELETE/MERGE)和TRIGGER均触发,语句编译为exec_stmt_treebased
BC054 Simple expr check occurs exeception.YY(本次新增)Y(局部treebased)简单表达式求值检查异常,如1/0,语句编译为exec_stmt_treebased
BC055 function(%u) with outparam called in simple expr does not support.YY(本次新增)Y(局部treebased)带出参函数在SELECT表达式中调用,语句编译为exec_stmt_treebased
BC061 not spport func with out param used in execsql statementYY(本次新增)Y(局部treebased)带出参函数在PERFORM语句中使用,日志有拼写错误"spport"
BC074 This function can not supports bytecode...Please check behavior_compat_flags.Y兼容性参数要求Y(本次新增)Nproc_outparam_override关闭时,带出参函数整体不支持
BC030 Stmt GOTO does not support with some errorYY(本次新增)NGOTO语句导致整个存储过程不支持字节码
BC068 ...nor subtype.Y不支持子程序Y(已命中+本次新触发)N同package内定义的subtype或匿名块内subtype也会触发
BC077 bulk collect does not support.Y不支持BULK COLLECTY(已命中)Y(局部treebased)SELECT BULK COLLECT和FETCH BULK COLLECT均触发,语句编译为exec_stmt_treebased

总结

本次测试新命中了6个之前未覆盖的BC日志码(BC053、BC054、BC055、BC061、BC074、BC030),并发现了BC068的新触发场景(同package subtype)。加上上一篇已命中的,目前二进制中grep出的约50条BC日志码中,已命中约20条。

未能命中的BC日志码大致可分为三类:

  1. 内部错误类(BC016/BC017/BC018/BC076/BC078):属于代码内部的异常处理路径,正常SQL无法触发
  2. 编译报错类(BC058/BC060/exit label):代码本身就不合规,创建时或执行时就报错了,字节码引擎来不及处理
  3. 实际支持类(BC020/BC022/BC023/BC028/BC037/BC038/BC041~BC049/BC052等):测试了各种场景都正常支持字节码,可能这些日志只在特定内部条件下触发,或者对应的功能在507版本已经支持但日志保留了下来

一个比较有意思的发现是,很多不支持场景并不是"全有或全无"的,而是"局部treebased"——不支持的语句编译为exec_stmt_treebased指令(字节码引擎执行到该指令时调用AST引擎处理这一条语句),存储过程整体仍然走字节码框架。真正导致整个存储过程不支持字节码的只有以下场景:

  • BC003: TRIGGER
  • BC004: 返回集合的函数(returns setof/table)
  • BC007: 参数默认值为PACKAGE变量
  • BC027 + BC030: FORALL with SAVE EXCEPTIONS
  • BC039: (汇总日志,表示该函数不支持)
  • BC057: 带出参的FUNCTION返回复合类型
  • BC066: 对入参进行赋值
  • BC068: 嵌套子程序或同package/匿名块内subtype
  • BC070: 伪类型参数
  • BC074: behavior_compat_options不满足要求
  • BC030: GOTO语句

换句话说,只要避开以上场景,存储过程基本都能走字节码框架,即使内部有些语句以treebased方式执行,整体性能仍然会有提升。

本文由 wps灵犀claw 使用 GLM-5.2 模型 测试及编写,使用提示词:

“C:\work\github\gaussdb-bytecode-test2
读下这个目录里的两篇文章,在第二篇的约束里,我本想明确穷举出哪些是不支持的,或者命中所有的从gaussdb二进制程序里grep出来的字节码日志,但官方文档和日志描述都不是太完善,成了个遗憾,你看看能不能帮我造一些测试用例再更多我没有命中到的日志。数据库连接信息我放setup.cfg里了,你可以用这个目录里的gaussdbjdbc.jar这个jdbc驱动连接数据库执行测试用例。注意不要修改我文章里的内容,测试过程你可以单独记录到第三篇文章里”

测试用例我大概肉眼核对了一下,没有大问题,小问题基本不影响结论,虽然AI又测出了几个日志,但是仍然有大量日志未触发。
本文仅供参考,实际字节码功能的覆盖场景以华为官方口径为准。
关于GaussDB的PLSQL字节码能力暂时就写到这,后续如果有新的发现足够写一篇新的文章就再来第四篇。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论