oracle存储过程转达梦8存储过程时踩过的坑2(完结篇)
之前写过一篇文章总结了oracle存储过程转达梦8存储过程时踩过的坑(https://www.cnblogs.com/kingstarer/p/13379053.html)
当时里面只总结了3个大坑,实际上我还碰到过不少小坑
因为这段时间,我们项目组决定使用java重写旧系统,放弃了原来使用存储过程那一套,所以最近就一直没再去整理之前的小坑。
今天正好记起来这事,就花点时间整理一下。虽然我已经不用到这些经验了,但希望对其他人有帮助。
(用java写逻辑比用存储过程方便好多,建议大家还是尽量放弃存储过程吧)
时区问题
达梦8安装后默认的时区,不是操作系统的时区,而是0时区。这会导致sysdate返回时间有误,需要修改/etc/dm_svc.conf文件,在文件中添加TIME_ZONE=(480)才正常,如下:
[root@ecs-htgx-0003 etc]# vi /etc/dm_svc.conf
# 以#开头的行表示是注释
# 全局配置区 dm_svc.conf
TIME_ZONE=(480)
LANGUAGE=(cn)DMHTGX=(192.168.0.137:5236)
# 服务配置区
[DMHTGX]
LOGIN_MODE=(2)
regexp_replace
达梦的正则匹配有问题,我踩的一个坑是这个:
select regexp_replace('CC4.city', '([(+-*/|><=,]|^)(.+)', '\2', 1, 1, 'i') from dual;
这个语句执行结果oracle跟达梦不一样
select regexp_replace('CC4.city', '([+-*]|^)(.+)', '\2', 1, 1, 'i') from dual; --输出 C4.city
--把+和-调换位置 oracle输出结果是一样的,但达梦却是不一样
select regexp_replace('CC4.city', '([-+*]|^)(.+)', '\2', 1, 1, 'i') from dual; --输出 CC4.city
仔细分析一下,是因为达梦把[]里面的+号字符,认为是正则表达式的元字符+(匹配前面的子表达式一次或多次)
级联删除问题
oracle用户迁移到达梦数据库后,发现多了好多触发器。仔细看了一下代码,应该是实现外键case delete的。估计是达梦不支持外键级联删除,在迁移时自动把这些级联删除改成触发器。
不过改成触发器后,就无法实现oracle的延迟约束功能了(alter session set constraints=deferred)
这个问题无解
BULK COLLECT问题
使用BULK COLLECT的查询语句,查不到记录时行为不同:oracle的BULK COLLECT查询默认是不会抛出no_data_found异常的,而达梦会。
解决方法是捕获no_data_found异常后做忽略处理。
DBMS_SQL包问题
DBMS_SQL有bug呀,获取出来的col_max_len是0,例子如下:
create table mydual as select * from dual; declare v_col_cnt NUMBER; v_cursorid NUMBER; v_desc_t DBMS_SQL.desc_tab2; begin dbms_output.enable; v_cursorid := DBMS_SQL.open_cursor; DBMS_SQL.parse(v_cursorid, 'select ''123'' c1, DUMMY c2 from mydual', dbms_sql.native); DBMS_SQL.describe_columns(v_cursorid, v_col_cnt, v_desc_t); FOR i IN 1..v_col_cnt LOOP dbms_output.put_line('i ' || i || ' name = ' || v_desc_t(i).col_name || ' col_max_len = ' || v_de 56c sc_t(i).col_max_len); END LOOP; end;
DBMS_SQL这个包还有其它好多bug,具体我没记下来,大家使用小心点了。
prior和next问题
当下标值在容器中找不到时,达梦无法正确获取prior和next,验证的存储过程如下:
declare type v_mp_type is table of number index by PLS_INTEGER; v_mp v_mp_type; begin dbms_output.enable; v_mp(1) := 1; v_mp(3) := 2; -- oracle输出1 达梦输出空 dbms_output.put_line('v_mp.prior(2) = ' || v_mp.prior(2)); end;
解决方法是自己写prior和next函数:
-- 需要写函数代替oracle的prior和next function get_prior_index(v_mp IN v_mp_type, v_ind IN PLS_INTEGER) return PLS_INTEGER is v_vv_last PLS_INTEGER := null; vv PLS_INTEGER := v_mp.first; begin -- 遍历v_mp 做比较 while vv is not null loop -- 如果发现某个下标值比传进来的v_ind大或者相等 则返回上一个下标值 -- (如果是第一个下标则返回NULL) if (vv >= v_ind) then return v_vv_last; end if; v_vv_last := vv; vv := v_mp.next(vv); end loop; -- 如果遍历完所有下标,仍未找到大于等于v_ind的值,则返回最大的下标v_mp.last return v_vv_last; end; function get_next_index ad8 (v_mp IN v_mp_type, v_ind IN PLS_INTEGER) return PLS_INTEGER is v_vv_last PLS_INTEGER := null; vv PLS_INTEGER := v_mp.last; begin -- 反序遍历v_mp 做比较 while vv is not null loop -- 如果发现某个下标值小于等于v_ind 则返回上一个下标值 --(如果是最大的下标则返回NULL) if (vv <= v_ind) then return v_vv_last; end if; v_vv_last := vv; vv := v_mp.prior(vv); end loop; -- 如果反序遍历完所有下标,仍未找到小于等于v_ind的值,则返回最小的下标v_mp.first return v_vv_last; end;
日期计算问题
这个网上有很多文章介绍过了,达梦默认两个整数相除,结果类型还是整数,而oracle是小数。
所以在oracle我们可以使用trunc(v_date)-1/86400获取1秒前的时间,但在达梦,这样写跟trunc(v_date) - 0是一样的。
解决方法是改成trunc(v_date)-1.0/86400
出参问题
如果把一个变量传给一个函数做为函数出参,以获取函数返回值,oracle默认会把这个函数清空,而达梦不会。
这就导致一个问题,
验证代码如下:
/*测试出参 在oracle期待输出为空 但是达梦会出现error*/ create or replace procedure testKinstarerOutParam(str OUT varchar2) as begin dbms_output.put_line('str = ' || str); if (str is not null) THEN RAISE_APPLICATION_ERROR(-20001, '出参没有清空'); end if; end; / create or replace procedure testKinstarerCallOutParam as strIn varchar2(64) := 'error'; begin testKinstarerOutParam(strIn); end; / dbms_output.enable; begin testKinstarerCallOutParam(); end;
lob支持问题
oracle可以使用to_char函数对lob类型字段操作,但在达梦,有时这样操作会失败,报错为DBMS_LOB.READ line 1157
diutil包缺失
不知道为什么,达梦没有提供diutil包。里面有一些函数,挺方便,没有真可惜。所以我自己写了一个
CREATE OR REPLACE PACKAGE diutil IS -- bool_to_int: translates 3-valued BOOLEAN TO NUMBER FOR USE -- IN sending BOOLEAN parameter / RETURN VALUES -- BETWEEN pls v1 (client) AND pls v2. since sqlnet -- has no BOOLEAN bind variable TYPE, we encode -- booleans AS false = 0, true = 1, NULL = NULL FOR -- network transfer AS NUMBER -- FUNCTION bool_to_int( b BOOLEAN) RETURN NUMBER; -- int_to_bool: translates 3-valued NUMBER encoding TO BOOLEAN FOR USE -- IN sending BOOLEAN para 2080 meter / RETURN VALUES -- BETWEEN pls v1 (client) AND pls v2. since sqlnet -- has no BOOLEAN bind variable TYPE, we encode -- booleans AS false = 0, true = 1, NULL = NULL FOR -- network transfer AS NUMBER -- function int_to_bool( n NUMBER) return boolean; function get_sql_hash(name IN varchar2, v_hash OUT RAW, pre10ihash OUT number) return number; function rpad_dm(string varchar2, padded_length number, pad_string varchar2 := ' ') return varchar2; function copy1kList(v_input ua_utl_def.t_str_1k_list) return ua_utl_def.t_str_1k_list; end diutil; CREATE OR REPLACE PACKAGE BODY diutil IS -------------------- -- bool_to_int -------------------- FUNCTION bool_to_int(b BOOLEAN) RETURN NUMBER IS BEGIN IF b THEN RETURN 1; ELSIF NOT b THEN RETURN 0; ELSE RETURN NULL; END IF; END bool_to_int; -------------------- -- int_to_bool -------------------- FUNCTION int_to_bool(n NUMBER) RETURN BOOLEAN IS BEGIN IF n IS NULL THEN RETURN NULL; ELSIF n = 1 THEN RETURN true; ELSIF n = 0 THEN RETURN false; ELSE RAISE value_error; END IF; END int_to_bool; function get_sql_hash(name IN varchar2, v_hash OUT RAW, pre10ihash OUT number) return number IS v_hash_varchar2 VARCHAR2(128); v_hash_tmp VARCHAR2(128); BEGIN -- Compute a hash value for the given string using md5 algo -- Input arguments: -- name - The string to be hashed. -- hash - An optional field to store all 16 bytes of returned -- hash value. -- pre10ihash - An optional field to store the pre 10i database -- version hash value. -- Returns: -- A hash value (last 4 bytes) based on the input string. -- The md5 hash algorithm computes a 16 byte hash value, but -- we only return the last 4 bytes so that we can return an -- actual number. One could use an optional RAW parameter to -- get all 16 bytes and to store the pre 10i hash value of 4 -- 4 bytes in the pre10ihash optional parameter. -- Utl_Raw.Cast_To_Raw( v_hash_varchar2 := DBMS_OBFUSCATION_TOOLKIT.MD5(name); v_hash := Utl_Raw.cast_to_raw(v_hash_varchar2); v_hash_tmp := substrb(v_hash, 13, 4); pre10ihash := to_number(v_hash_tmp, 'XXXXXXXXXX'); --TODO: 这里实现有问题 pre10ihash是啥意思我没看懂 -- select Utl_Raw.Cast_To_Raw(DBMS_OBFUSCATION_TOOLKIT.MD5(input_string =>'abc')) a from Dual return to_number(v_hash_tmp, 'XXXXXXXXXX'); END; function rpad_dm(string varchar2, padded_length number, pad_string varchar2 := ' ') return varchar2 IS v_len number := lengthb(string); BEGIN dbms_output.put_line('v_len - padded_length = ' ); if padded_length < v_len THEN return substrb(string, 1, padded_length); --如果输入长度小于原字符串长度,则调用substrb截断 elsif padded_length = v_len THEN return string; --如果长度相等直接返回原串即可 else return string || rpad(' ', padded_length - v_len, pad_string); --如果长度大于原字符串,则在后面补空格 end if; END; function copy1kList(v_input ua_utl_def.t_str_1k_list) return ua_utl_def.t_str_1k_list IS v_tmplist ua_utl_def.t_str_1k_list; v_ind PLS_INTEGER; begin if v_input.count > 0 then /* for vv in v_input.first .. v_input.last LOOP v_tmplist(vv) := v_input(vv); end loop; */ v_ind = v_input.first; while v_ind is not null loop v_tmplist(v_ind) := v_input(v_ind); v_ind = v_input.next(v_ind); end loop; end if; return v_tmplist; end; end diutil;
存储过程建失败不会提示
在达梦客户端执行新建存储过程时需要注意,即使创建成功了,也只代表语法正确。很可能存储过程有其它问题导致没建成功,仍是无效状态。
解决方法是创建存储过程之后再手动执行 alter PROCEDURE 存储过程名称 compile;
FORMAT_ERROR_BACKTRACE没有调用处行号问题
众所周知,oracle提供一个函数dbms_utility.format_error_backtrace,用于获取异常模块处理时调用,获取函数堆栈信息,里面会有明确的函数名称和源码位置信息
但达梦调用这个函数返回的是一堆看不懂的内部符号
这个问题对我迁移造成不少困扰,因为我们业务的主要逻辑就是在存储过程里面实现的。我们需要在程序出异常时登记日志,记录函数堆栈信息,以方便跟踪。
经过我不懈研究,终于解决了达梦无法获取堆栈信息的问题,这里跟大家分享一下解决方法:
dbms_output.enable; select * from q$log order by 1 desc; select * from q$error_instance order by 1 desc; CREATE OR REPLACE PROCEDURE logIntoDb(loglevel PLS_INTEGER, inf IN varchar2, callStack IN varchar2) IS PRAGMA AUTONOMOUS_TRANSACTION; --日志登记需要使用自治事务 BEGIN -- loglevel 0 debug 10 inf 20 err INSERT INTO q$log (id, "CONTEXT", text, call_stack, created_on, created_by, app_system, app_module) VALUES (q$log_seq.nextval, decode(logLevel, 0, 'debug', 'other'), inf, callStack, SYSDATE, USER, 'unify_audit', 'logIntoDb'); commit; END; alter PROCEDURE logIntoDb compile; CREATE OR REPLACE FUNCTION getErrorBackTrace() return varchar2 IS -- 达梦不能直接获取堆栈信息,需要套在函数里面 c_stack VARCHAR2(6000) := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; BEGIN return c_stack; END; / alter FUNCTION getErrorBackTrace COMPILE; CREATE OR REPLACE PROCEDURE debugHt(inf IN varchar2) IS -- 默认不使用异常 这样不能记录行号 -- 使用异常可以记录行号但性能会下降,用于调试 v_useException boolean := true; BEGIN if (v_useException) then -- 主动创建一个异常,这样才可以FORMAT_ERROR_BACKTRACE函数才有值 RAISE_APPLICATION_ERROR(-20001, 'debug'); else logIntoDb(0, inf, DBMS_UTILITY.format_call_stack); end if; exception when others then -- 达梦的DBMS_UTILITY.FORMAT_ERROR_BACKTRACE函数必须隔位获取 -- 不然只能获取当前函数的堆栈信息 logIntoDb(0, inf, getErrorBackTrace()); END; / alter PROCEDURE debugHt COMPILE; CREATE OR REPLACE PROCEDURE proc2 IS BEGIN debugHt('hello log'); execute immediate 'delete * from dual1233'; exception when others then debugHt('hello exp'); END; / alter PROCEDURE proc2 COMPILE; CREATE OR REPLACE PROCEDURE proc3 IS BEGIN proc2(); END; / CREATE OR REPLACE PROCEDURE proc4 IS BEGIN proc3(); END; / begin proc4(); end;
- 菜鸟从这里起飞,版本控制四【完结篇:应用】_AX
- 我与PB有个约会 - 第五季:完结篇
- 不要重复发明轮子:C++重用的5重境界(5)——消息通信(完结篇)
- 黄聪:C#反射 Reflection学习随笔(完结篇)_AX
- Spring温故知新(九)Spring自动代理 (系列第一部分完结篇)
- Android 实现书籍翻页效果----完结篇
- 常用串行EEPROM的编程应用(三)完结篇
- WinCE6 RIL驱动开发日志(六)——完结篇
- 数据库设计Step by Step (11)——通用设计模式(系列完结篇)
- 直接拿来用!最火的Android开源项目(完结篇)
- 《程序猿的搬砖生活》十、《完结篇》搬砖不是结束而是开始
- 四天精通shell编程(四)--完结篇
- 实现app上对csdn的文章查看,以及文章中图片的保存 (制作csdn app 完结篇)
- 直接拿来用!最火的Android开源项目(完结篇)
- 怎样成为高级的学习者?(4· 完结篇):融合
- 直接拿来用!最火的Android开源项目(三,完结篇)
- 通过逆向学习软件设计(5)【完结篇】
- linux调度器_第三代cfs(4)_总手稿_完结篇
- ORACLE SQL性能优化系列 (十四) 完结篇
- Android之RecyclerView简单使用(完结篇)