1. 程式人生 > >Oracle Exception 異常處理

Oracle Exception 異常處理

Oracle 異常處理

一、概述
異常分成三大類:預定義異常、非預定義異常、自定義異常
處理方法分為:直接丟擲異常、內部塊處理異常、遊標處理異常

預定義異常:由PL/SQL定義的異常。由於它們已在standard包中預定義了,因此,這些預定義異常可以直接在程式中使用,而不必再定義部分宣告。

非預定義異常:用於處理預定義異常所不能處理的Oracle錯誤。

自定義異常:使用者自定義的異常,需要在定義部分聲明後才能在可執行部分使用。使用者自定義異常對應的錯誤不一定是Oracle錯誤,例如它可能是一個數據錯誤。

三種異常中,預定義與非預定義異常都與Oracle錯誤有關,並且由Oracle隱含自動丟擲,
而自定義異常與Oracle錯誤沒有任何關聯,由開發人員為特定情況所定義的異常,需要顯式丟擲(raise)。


二、預定義異常

1、預定義異常的種類
1.1、ACCESS_INTO_NULL
說明:對應於ORA-06530,當建立物件型別應用是,在引用物件屬性之前,如果沒有初始化物件,
直接為物件屬性賦值,則出發此異常。
例子:create type emp_type as object(name varchar2(10),sal number(6,2));
declare
emp emp_type;
begin
emp.name:='scott';
exception
when ACCESS_INTO_NULL dbms_output.put_line('未初始化');
end;

1.2、CASE_NOT_FOUND
說明:對應於ORA-06592,編寫CASE語句時,如果在when子句中沒有包含必須的條件分支並且沒有包含else子句,就會出發此異常。

1.3、COLLECTION_IS_NULL
說明:對應於ORA-06531,在集合元素(巢狀表或VARRAY)賦值時,如果沒有初始化集合元素,則會出發此異常。
例子:declare
type ename_table_type is table of emp.ename%type;
ename_table ename_table_type;
begin
select ename into ename_table(2) from emp where empno=&no;
dbms_output.put_line(name_table(2));
exception
when COLLECTION_IS_NULL then dbms_output.put_ling('未初始化');
end;

1.4、CURSOR_ALREADY_OPEN
說明:ORA-06511,當開啟已經開啟的遊標時,會觸發此異常。

1.5、DUP_VAL_ON_INDEX
說明:對應於ORA-00001,當在唯一索引所對應的列上鍵入重複值時,會觸發此異常。

1.6、INVALID_CURSOR
說明:對應於ORA-01001,當試圖在不合法的遊標上執行操作時,會觸發此異常。例如從未開啟的遊標提取資料或關閉未開啟的遊標。

1.7、INVALID_NUMBER
說明:對應於ORA-01722,當內嵌sql語句不能有效地將字元轉變成數字時,會隱含地觸發此異常。

1.8、NO_DATA_FOUND
說明:對應於ORA-01403,當執行selct into未返回行,或者引用了索引表未初始化元素時,會觸發此異常。

1.9、TOO_MANY_ROWS
說明:對應於ORA-01422,當執行select into返回超過一行,則觸發此異常。

1.10、ZERO_DIVIDE
說明:對應於ORA-01476,當除數為0時,觸發此異常。

1.11、SUBSCRIPT_BEYOND_COUNT
說明:對應於ORA-06533,當使用巢狀表或VARRAY元素時,如果元素下標超出範圍,會觸發此異常。
例子:declare
type emp_array_type is varray(20) of varchar2(10);
emp_array emp_array_type;
begin
emp_array:=emp_array_type('scott','mary');
dbms_output.put_line(emp_array(3));
exception
when SUBSCRIPT_BEYOND_COUNT then dbms_output.put_line('超出範圍');
end;

1.12、SUBSCRIPT_OUTSIDE_LIMT
說明:對應於ORA-06532,當使用巢狀表或VARRAY元素時,如果下標為取負數,會觸發此異常。

1.13、VALUE_ERROR
說明:對應於ORA-06502,當執行賦值操作時,如果變數長度不足以容納實際資料,會觸發此異常。

1.14、LOGIN_DENIED
說明:對應於ORA-01017,當連線Oracle資料庫時,如果使用者名稱或密碼不正確,會觸發此異常。

1.15、NOT_LOGGED_ON
說明:對應於ORA-01012,如果在沒有連線到資料庫的情況下,執行PL/SQL塊,會觸發此異常。

1.16、PROGRAM_ERROR
說明:對應於ORA-06501,如果出現此錯誤,則表示存在PL/SQL內部問題,使用者此時可能需要重新安裝資料字典和PL/SQL系統包。

1.17、ROWTYPE_MISMATCH
說明:對應於ORA-06504,當執行賦值操作時,如果宿主遊標變數和PL/SQL遊標變數的返回型別不相容,會觸發此異常。

1.18、SELF_IS_NULL
說明:對應於ORA-30625,當使用物件型別時,如果在null例項上呼叫成員方法,會觸發此異常。

1.19、STORAGE_ERROR
說明:對應於ORA-06500,PL/SQL塊執行時,如果超出記憶體空間或記憶體被損壞,會觸發此異常。

1.20、SYS_INVALID_ROWID
說明:對應於ORA-01410,當將字元轉變為ROWID時,如果使用了無效的字串,會觸發此異常。

1.21、TIMEOUT_ON_RESOURCE
說明:對應於ORA-00051,如果Oracle在等待資源時出現超時錯誤,會觸發此異常。

2、預定義異常的處理

說明:這裡說兩個常見的異常no_data_found和too_many_rows,這兩個異常多由select into語句觸發。
舉例:
declare
v_cnt number :=800;
v_name emp.ename%type;
begin
select ename into v_name from emp where sal=v_cnt;
dbms_output.put_line('姓名:' || v_name);
exception
when no_data_found then --直接丟擲異常
dbms_output.put_line('不存在該工資值的僱員');
when too_many_rows then
dbms_output.put_line('存在多個僱員具有該工資');
when others then
rollback;
dbms_output.put_line('異常回滾退出');
end;

2.1、no_data_found異常
起因:給一個變數賦值時,查詢的結果為空。
說明:如上面例子可以看到,一旦直接丟擲異常,就會讓過程中斷。no_data_found這種異常,沒有嚴重到要讓程式中斷的地步,可以完全交給由程式進行處理。

2.1.1、使用內部獨立塊處理
說明:這是一種比較好的處理方式了,不會因為這個異常而引起程式中斷。
例如:
declare
v_cnt:=800;
v_name emp.ename%type;
begin
begin
select ename into v_name from emp where sal=v_cnt;
exception
when no_data_found then
v_name:='';
end;
dbms_output.put_line('姓名:' || v_name);
exception
when too_many_rows then
dbms_output.put_line('存在多個僱員具有該工資');
when others then
rollback;
dbms_output.put_line('異常回滾退出');
end;

2.1.2、使用遊標處理
說明:遊標操作可以完全避免no_data_found異常。
例如:
declare
v_cnt:=800;
v_name emp.ename%type;
cursor c_cursor is select ename from emp where sal=v_cnt;
begin
open c_cursor;
fetch c_cursor into v_name;
close c_cursor;
dbms_output.put_line('姓名:' || v_name);
exception
when too_many_rows then
dbms_output.put_line('存在多個僱員具有該工資');
when others then
rollback;
dbms_output.put_line('異常回滾退出');
end;

2.2、too_many_rows異常
起因:給一個變數賦值時,查詢的結果有多條記錄或select into 語句中變數名與表名相同。
說明:返回多條記錄如果是可接受的,必須採用遊標處理;如果是不可接受的,必須採用內部快處理。

(1)、返回多條記錄如果是可接受的,則隨便取一條,用遊標處理。

(2)、返回多條記錄如果是不可接受的,則必須捕獲異常,用內部塊處理。
舉例:
declare
v_cnt:=800;
v_name emp.ename%type;
begin
begin
select ename into v_name from emp where sal=v_cnt;
exception
when no_data_found then
v_name:='';
when too_many_rows then
v_name:='';
dbms_output.put_line('存在多個僱員具有該工資');
end;
dbms_output.put_line('姓名:' || v_name);
exception
when others then
rollback;
dbms_output.put_line('異常回滾退出');
end;


三、非預定義異常
說明:非預定義異常用於處理與21個預定義異常無關的Oracle錯誤。
由於預定義異常只是與一部分Oracle錯誤相連的異常,所以如果要處理沒有與預定義異常對應的Oracle錯誤時,
則需要為這些Oracle錯誤宣告相應的非預定義異常。宣告這樣的異常需要使用exception_init編譯指令。

exception_init編譯指令的定義如下:
pragma exception_init(exception_name,Oracle_error_number);
exception_name是預先被宣告的異常名,Oracle_error_number是錯誤號,這條命令必須寫在定義部分。

例子:
declare
e_inte exception; --定義
pragma exception_intt(e_inte,-2291); --關聯Oracle錯誤ORA-2291
begin
update emp set deptno=&dno where empno=&eno;
exception
when e_inte then
dbms_output.put_line('部門不存在');
end;

注意,通過exception_init,一個自定義異常只能和一個Oracle錯誤相連,在異常處理語句中,
sqlcode和sqlerrm將返回這個Oracle錯誤的程式碼和訊息文字,而不是返回使用者自定義訊息


四、自定義異常

說明:自定義異常與Oracle錯誤沒有任何關聯,由開發人員為特定情況所定義的異常,需要顯式丟擲(raise)。

1、自定義異常的簡述
儘管自定義異常的宣告與變數的宣告類似,但異常是一個錯誤狀態,而不是一個數據項,
所以異常不能出現在賦值語句和sql語句中,但異常的作用域與定義部分其它變數的作用域相同。
如果一個自定義異常被傳遞到作用域外,則不能再通過原來的名字引用它。為了解決這個問題,
我們可以在包中宣告異常,這個異常就可以在任何塊中使用,使用時在異常前加包名字首即可。

自定義異常由raise語句產生(由exception_inti編譯指令宣告的使用者自定義異常也可通過對應的Oracle錯誤的出現而產生),
當然如果需要,預定義異常也可以使用raise語句來產生。

當一個異常產生是,控制權立即轉交給塊的異常處理部分。如果該塊沒有異常處理部分,
則向該塊的外一層塊傳遞。一旦控制權交給了異常處理部分,則再沒有辦法回到塊可執行部分。

一條異常處理語句可以處理多個異常,只要在when子句中由or分割多個異常即可。

如果塊中的異常沒有被處理,則該塊會帶著未處理的異常返回呼叫它的程式,這會導致呼叫它的程式出錯。
如果在儲存過程中出現異常,則儲存過程的out引數將得不到返回值。為了避免未處理異常帶來的弊病,
我們最好在塊的最外層使用others子句處理塊中所有未處理的異常。這樣就可以確保所有的錯誤都能被發現和處理。

如果是定義部分的一個賦值語句產生了異常。即使在當前塊的異常處理部分中有處理該異常的處理語句,也不去執行,
而是立刻被傳遞到外部塊中。當異常傳遞到外部塊中以後,按照處理可執行部分中產生的異常一樣去處理該異常。

在異常處理語句中也可以產生異常,這個異常可以通過raise語句產生,或是由於出現一個執行錯誤而產生。
這兩種情況下產生的異常都被立刻傳遞到塊外,這與定義部分產生的異常一樣。為什麼這樣處理呢?
因為異常部分每一次只能有一個異常被處理,當一個異常被處理是,產生了另外一個異常,
而一次不能同時處理多個異常,所以將異常處理部分產生的異常傳遞到塊外。

2、自定義異常的簡單應用
說明:使用者定義異常型別,使用raise顯示丟擲異常
declare
v_name:='mary';
v_dno:=80;
e_integrity exception; --定義自定義異常
e_no_rows exception;
pragma exception_init(e_integrity,-2291);
name emp.ename%type:=v_name;
dno emp.deptno%type:=v_dno;
begin
update emp set deptno=v_dno where ename=v_name;
if sql%notfound then
raise e_no_rows; --顯示丟擲異常
end if;
exception
when e_integrity then
dbms_output.put_line('該部門不存在');
when e_no_rows then
dbms_output.put_line('該僱員不存在');
end;

3、raise丟擲異常的三種方法
1)、Raise exception:用於丟擲當前程式中定義的異常或在 standard 中的系統異常。
2)、Raise package.exception:用於丟擲有一些異常是定義在非標準包中的,如UTL_FILE,DBMS_SQL以及程式設計師建立的包中異常
3)、Raise:不帶任何引數,這種情況只出現在希望將當前的異常傳到外部程式時。


五、異常的函式

說明:當出現異常,通過使用異常函式可以取得錯誤號及相關錯誤資訊,另外通過使用raise_application_error也可自定義錯誤號與錯誤資訊。

1、sqlcode和sqlerrm函式

sqlcode返回異常物件的錯誤程式碼號,sqlerrm返回的是對應的錯誤資訊,為了在plsql中處理其他未預料的Oracle錯誤,
可以在異常處理部分的when others子句後引用這兩個函式來確定錯誤號和資訊。

異常種類 SQLCODE SQLERRM
Oracle錯誤對應的異常 負數 Oracle錯誤
NO_DATA_FOUND +100 No data found
自定義異常 -1 User-Defined Exception
沒有產生異常 0 Oracle-0000

注意:如果使用exception_init預編譯指令宣告與Oracle錯誤相連的自定義異常,則SQLCODE和SQLERRM返回
對應的Oracle錯誤程式碼和相應的錯誤資訊,而不是返回+1和User-Defined。

如果SQLERRM是可以帶一個數字引數,返回值是與這個數字引數相關的文字。

如果要在sql語句中使用sqlcode和sqlerrm,則一定要先把它們的值賦給區域性變數,然後再將這些區域性變數用在sql語句中。
因為這些函式是過程性的,不能直接用在sql語句中。

EXCEPTION
WHEN OTHERS
THEN
ROLLBACK;
v_message := '錯誤行號:' || DBMS_UTILITY.format_error_backtrace () || '錯誤程式碼:'|| SQLCODE|| '錯誤提示'|| SQLERRM;
DBMS_OUTPUT.put_line (v_message);
END;

2、raise_application_error
說明:該過程用於自定義錯誤資訊,僅限資料庫端子程式使用(過程、函式、包、觸發器),不能在匿名塊或客戶端子程式中。。
語法:raise_application_error(error_number,mesage[,[true|false]]);
其中error_number定義錯誤號,必輸是-20000到-20999之間的負整數;message指定錯誤資訊,不長於2048位元組;
第三個為可選引數,true則錯誤會被放在先前錯誤的堆疊中,fale則替換先前所有錯誤,預設為false。

例子:
create or replace procedure raise_comn(eno number,commission number) is
v_comm emp.comm%type;
begin
select comm into v_comm from emp where empno=eno;
if v_comm is null then
raise_application_error(-20001,'該僱員無補助');
end if;
exception
when no_date_found then
dbms_output.put_line('僱員不存在');
end;


六、PL/SQL編譯警告
說明:10g後新增功能,為了提高plsql子程式的健壯性並避免執行錯誤,可以啟用警告檢查功能。

1、PL/SQL警告的分類
severs:檢查可能出現的不可預料的結果或錯誤結果,例如引數別名。
performance:檢查可能引起的效能問題,例如執行insert時為number列提供varchar2資料。
informational:檢查子程式中的死程式碼。
all:檢查所有警告(上面3種都檢查)。

2、PL/SQL警告訊息的控制
說明:為了啟用警告功能,需要設定初始化引數PLSQL_WARNINGS。
可以通過系統級、會話級、DBMS_VARNINGS系統包、ALTER PROCEDURE命令設定。
可以啟用或禁止所有警告或某種警告。

alter system set plsql_warnings='enable:all'; --系統級
alter session set plsql_warnings='enable:performance'; --會話級
call dbms_warning.set_warning_setting_string('enable:severs','session'); --DBMS_VARNINGS系統包
alter procedure hello compile plsql_warnings='enable:performance'; --ALTER PROCEDURE命令
alter session set plsql_warnings='disable:all';

3、PL/SQL警告的使用

1)、檢測死程式碼
create or replace procedure dead_code as
x number :=10;
begin
if x=10 then x:=20;
else
x:=100; --死程式碼,永遠不會執行
end if;
end dead_code;

alter session set plsql_warnings='enable:informational';--啟用
alter procedure dead_code compile;--編譯
show errors;--顯示

2)、檢測引起效能問題的程式碼
create or replace precedure update_sql(name varchar2,salary varchar2) is
begin
update emp set sql=salary where ename=name;
end;

alter session set plsql_warnings='enable:informational';--啟用
alter procedure dead_code compile;--編譯
show errors;--顯示