之前寫過一篇文章總結了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;
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/232999.html
標籤:其他
