本文根据日志描述反向倒推测试场景,尝试命中更多上一篇没有覆盖到的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/ROLLBACK |
treebased |
exec_stmt_treebased_transaction |
|
EXECUTE IMMEDIATE |
treebased |
动态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语句触发,原因各不相同:
|
BC001 |
只有非A兼容模式才会触发,当前环境为A模式 |
|
BC002 |
package初始化匿名块本身不作为字节码对象,内部procedure仍支持 |
|
BC008 |
无法通过正常DDL创建含"无效参数类型"的函数 |
|
BC016 |
processStatement异常,内部错误 |
|
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 |
表类型/复合类型出参均支持 |
|
BC033 |
GET DIAGNOSTICS对record变量字段赋值正常支持 |
|
BC034 |
RESULT_OID诊断类型正常支持 |
|
BC037 |
tableof类型转text正常支持 |
|
BC038 |
bytea类型转换正常支持 |
|
BC041/BC042/BC044 |
数组下标访问正常支持 |
|
BC043 |
无效字段名在运行时报错,不触发BC日志 |
|
BC045 |
XML类型操作正常支持 |
|
BC046 |
集合类型操作正常支持 |
|
BC047/BC049 |
cursor/record赋值均正常支持 |
|
BC048 |
无法创建含无效typeoid的对象 |
|
BC052 |
子查询作为表达式编译为exec_stmt_treebased,但非BC052 |
|
BC056 |
关闭proc_outparam_override时触发的是BC074而非BC056 |
|
BC058 |
函数无返回值时运行报错,dump显示Not supported但无BC日志 |
|
BC060 |
procedure带RETURN值时编译报错,无法执行 |
|
BC064 |
调用方不提供out参数时函数找不到 |
|
BC065 |
出参类型转换正常支持 |
|
BC067 |
系统函数调用时被当作普通函数调用,不影响调用方 |
|
BC071/BC073 |
表达式类型变更/复杂化未触发 |
|
BC075 |
变量数不匹配的EXECUTE INTO正常treebased |
|
BC076 |
processExpr异常,内部错误 |
|
BC078 |
栈大小异常,内部错误 |
|
BC079 |
权限回收后函数仍显示支持字节码 |
|
exit/continue label not match |
标签不匹配的procedure编译为invalid |
|
outparam only supports var & row type |
refcursor/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. |
Y |
Y(本次新增) |
Y(局部treebased) |
DML语句(INSERT/UPDATE/DELETE/MERGE)和TRIGGER均触发,语句编译为exec_stmt_treebased |
|
|
BC054 Simple expr check occurs exeception. |
Y |
Y(本次新增) |
Y(局部treebased) |
简单表达式求值检查异常,如1/0,语句编译为exec_stmt_treebased |
|
|
BC055 function(%u) with outparam called in simple expr does not support. |
Y |
Y(本次新增) |
Y(局部treebased) |
带出参函数在SELECT表达式中调用,语句编译为exec_stmt_treebased |
|
|
BC061 not spport func with out param used in execsql statement |
Y |
Y(本次新增) |
Y(局部treebased) |
带出参函数在PERFORM语句中使用,日志有拼写错误"spport" |
|
|
BC074 This function can not supports bytecode…Please check behavior_compat_flags. |
Y |
兼容性参数要求 |
Y(本次新增) |
N |
proc_outparam_override关闭时,带出参函数整体不支持 |
|
BC030 Stmt GOTO does not support with some error |
Y |
Y(本次新增) |
N |
GOTO语句导致整个存储过程不支持字节码 |
|
|
BC068 …nor subtype. |
Y |
不支持子程序 |
Y(已命中+本次新触发) |
N |
同package内定义的subtype或匿名块内subtype也会触发 |
|
BC077 bulk collect does not support. |
Y |
不支持BULK COLLECT |
Y(已命中) |
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方式执行,整体性能仍然会有提升。
转载:公众号DarkAthena
网硕互联帮助中心





评论前必须登录!
注册