zoukankan      html  css  js  c++  java
  • 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_desc_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(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 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 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;
    
    
    
  • 相关阅读:
    C#编程利器之二:结构与枚举(Structure and enumeration)
    解读设计模式模板方法模式(Template Method),电脑就是这样造出来的
    清空mysql一个库中的所有表
    在执行并行程序工程中,突然弹出 connection closed 窗口,随后 ssh 与服务器的连接断开,并行程序也中断
    菜鸟求救 myeclipse安装flex3插件的问题
    linux 下 将 shell script 与 一个桌面图标联系在一起 (2)
    MYSQL EXPLAIN语句的extended 选项学习体会
    MySQL 性能跟踪语句
    Flex Flash
    Flex Builder 3 正式版
  • 原文地址:https://www.cnblogs.com/kingstarer/p/14113209.html
Copyright © 2011-2022 走看看