Oracle數據庫第七課——Oracle中的異常處理

知識點:瞭解異常的處理、異常的作用域,以及異常的傳播。學習異常如何捕獲 PL/SQL 語句塊的聲明部分、可執行部分或者異常處理部分所發生的運行時錯誤。也學習如何定義自己的異常以及如何重新拋出異常。

 

1、理解異常

        在編寫 PL/SQL 程序時,避免不了會發生一些錯誤,可能是程序設計人員自己造成的,也可能是操作系統或硬件環境出錯,比如出現除數爲零、磁盤 I/O 錯誤等情況。對於出現的這次錯誤,Oracle 採用異常機制來處理,以長陽處理代碼通常放在 PL/SQL 的 EXCEPTION代碼塊中。根據異常產生的機制和原理,可將 Oracle 系統異常分爲以下兩大類:

預定義異常:Oracle 系統自身爲用戶提供了大量的、可在 PL/SQL 中使用的預定義異常,以便檢查用戶代碼失敗的一般原因。他們都定義在 Oracle 的核心 PL/SQL 庫中,用戶可以在自己的 PL/SQL 異常處理部分使用名稱對其進行標識。對這種異常情況的處理,用戶無須再程序中定義,他們有 Oracle 自動引發。

自定義異常:有時候可能會出現操作系統錯誤或機器硬件故障,這些錯誤 Oracle 系統自身無法知曉,也不能控制。例如,操作系統因病毒破壞而產生故障、磁盤村壞、網絡突然中斷等。另外,因業務的實際需求,程序設計人員需要自定義一些錯誤的業務邏輯,而PL/SQL 程序在運行過程中就可能會觸發到這些錯誤的業務邏輯。那麼,對於以上這些異常情況的處理,就需要用戶在程序中自定義異常,然後由 Oracle 自動引發。

1.1    預定義異常

       當 PL/SQL 程序違反了 Oracle 系統內部規定的設計規範時,就會自動引發一個預定義的異常。例如,當除數爲零時,就會引發“ZERO_DIVIDE”異常。Oracle 系統常見的預定義異常及其說明如下表:

錯誤代碼

命名的系統異常

產生原因

ORA-6530

ACCESS_INTO_NULL

未定義對象

ORA-6531

COLLECTION_IS_NULL

集合元素未初始化

ORA-6511

CURSER_ALREADY_OPEN

遊標已經打開

ORA-0001

DUP_VAL_ON_INDEX

唯一索引對應的列上有重複的值

ORA-1001

INVALID_CURSOR

在不合法的遊標上進行操作

ORA-1722

INVALID_NUMBER

內嵌的SQL語句不能將字符轉換爲數字

ORA-1403

NO_DATA_FOUND

使用select into未返回行,或應用索引表未初始化的元素時

ORA-1422

TOO_MANY_ROWS

執行select into時,結果集超過一行

ORA-1476

ZERO_DIVIDE

除數爲0

ORA-6533

SUBSCRIPT_BEYOND_COUNT

元素下標超過嵌套表或VARRAY的最大值

ORA-6532

SUBSCRIPT_OUTSIDE_LIMIT

使用嵌套表或VARRAY時,將下標指定爲負數

ORA-6502

VALUE_ERROR

賦值時,變量長度不足以容納實際數據

ORA-1017

LOGIN_DENIED

PL/SQL應用程序連接到oracle數據庫時,提供了不正確的用戶名或密碼

ORA-1012

NOT_LOGGED_ON

PL/SQL應用程序在沒有連接oralce數據庫的情況下訪問數據

ORA-6501

PROGRAM_ERROR

PL/SQL內部問題,可能需要重裝數據字典&PL/SQL系統包

ORA-6504

ROWTYPE_MISMATCH

宿主遊標變量與PL/SQL遊標變量的返回類型不兼容

ORA-6500

STORAGE_ERROR

運行PL/SQL時,超出內存空間

ORA-0051

TIMEOUT_ON_RESOURCE

Oracle在等待資源時超時

示例練習1:程序嘗試除以0,從而引發系統異常。

declare
  v_i int := 12;  
begin
  v_i := v_i / 0;
  dbms_output.put_line(v_i);
end;

思考:遇到這樣的異常,如何處理?

declare
  v_i int := 12;  
begin
  v_i := v_i / 0;  
  dbms_output.put_line(v_i);
exception
  when zero_divide then
  dbms_output.put_line('除數不能爲0');
end;

輸出結果:“除數不能爲0”。

 

1.2    自定義異常

       Oracle 系統內部的預定義異常僅僅 20 個左右,而實際程序過程中可能會產生幾千個異常情況,爲此 Oracle 經常使用錯誤編號和相關描述輸出異常信息。另外,程序設計人員可能會根據實際的業務需求定義一些特殊異常,這樣 Oracle 的自定義異常就可以分爲錯誤代碼異常和業務邏輯異常兩種。

1.2.1 錯誤編號異常(非預定義異常)

       錯誤編號異常,通常稱爲非預定義異常,是指在 Oracle 系統發生錯誤時,系統會顯示錯誤編號和相關描述信息的異常。雖然可以直接使用錯誤編號來處理異常,但錯誤編號爲一串數字,較爲抽象,不易於記憶和理解。於是用戶可以首先在 PL/SQL 塊的聲明部分( declare 部分)使用 exception 類型定義一個異常變量名,然後使用語句pragma exception_init  錯誤編號”關聯這個異常變量名。 具體步驟如下:

1. 在 PL/SQL 塊的定義部分定義異常情況:
<異常情況名> EXCEPTION;
2. 將其定義好的異常情況,與標準的 ORACLE 錯誤聯繫起來,使用 EXCEPTION_INIT
語句:
PRAGMA EXCEPTION_INIT(<異常情況名>, <錯誤代碼>);
3. 在 PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。

示例練習2:非預定義異常的練習

要求:scott 模式下,向 emp 表中插入一行新記錄,新記錄員工編號與表中已有員工 編號重複,引起違反唯一條件約束”異常。

實現步驟:

第一步:使用Scott用戶登錄PL/SQL Developer,新建SQL窗口,並使用“select * from emp; ”語句,查看一下emp表的基本信息。

第二步:在插入數據的時候,使用異常處理。

第三步:查看輸出結果。

總結:異常處理部分一般放在PL/SQL程序體的後半部

結構爲:

exception
  when 異常1 then 執行語句1 ;
  when 異常2 then 執行語句2 ;
  ……
  when others then 執行語句 ;
end;

說明:異常處理可以按任意次序排列,但 others 必須放在最後。

 

1.2.1  業務邏輯異常

        在實際的應用中,例如,自己的程序會要求用戶輸入學生的 ID 值。然後,這個值被賦予程序後面會使用的變量 v_id。通常,希望學生 ID 的值是正值。但可能,用戶在操作時輸入了一個負數。在編寫的程序邏輯中這種情況是不被允許的。由於變量 v_id 被定義爲數值類型,而且負數也是一個合法數值,所以程序不會給出任何錯誤信息。因此,開發人員需要根據這一具體的業務邏輯規則自定義一個業務邏輯異常來防止該錯誤的發生並提示用戶。

        無論是預定義異常,還是錯誤編號異常,都是 Oracle 系統判斷的錯誤,但業務邏輯異常是 Oracle 系統本身無法知道的,這樣就需要有一個引發異常的機制,引發業務邏輯異常通常使用 raise 語句來實現。當引發一個異常時,控制就會轉到exception 異常處理部分執行異常處理語句。業務邏輯異常首先在 declare 部分使用 exception 類型聲明一個異常變量,然後在 begin 部分根據一定的業務邏輯規則執行 raise 語句(在 raise 關鍵字後面跟着異常變量名),最後在 exception 部分編寫異常處理語句。

具體步驟如下:

(1)在 PL/SQL 塊的定義部分定義異常情況:
    <異常情況名> EXCEPTION;
(2)RAISE <異常情況>; 
(3)在 PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。

示例練習3:業務邏輯異常練習

要求:提示用戶輸入年齡,默認合法年齡是0—130之間,如果用戶輸入的數字超出範圍,則觸發異常

示例完整代碼:

--業務邏輯異常處理
declare
  v_age number(3,0) := &age ;--將用戶輸入的數字,存儲在變量v_age中
  ageSmall_exception exception ;--聲明一個異常,當用戶輸入數字過小時,觸發
  ageLarge_exception exception ;--聲明一個異常,當用戶輸入數字過大時,觸發
begin  
  if (v_age < 0 ) then
    raise ageSmall_exception;--raise關鍵字,觸發對應的異常
  elsif (v_age > 130) then
    raise ageLarge_exception;
  end if;
  dbms_output.put_line('您輸入的年齡是:' || v_age);
exception
  when ageSmall_exception then
    dbms_output.put_line('年齡不能小於0');--當ageSmall_exception異常被觸發時,執行語句
  when ageLarge_exception then
    dbms_output.put_line('年齡不能大於130');
end;

 

示例練習4:業務邏輯異常練習—更新emp表中指定員工的工資

要求:在scott模式下,將用戶輸入的員工編號,工資增加100

 

--2、提示用戶輸入員工編號,並給該員工的工資增加100元
declare
  v_empno emp.empno%type := &empno ;--將用戶輸入的員工編號,存儲在變量v_empno中
  no_result exception;--聲明異常   
begin
  update emp set sal = sal+100 where empno = v_empno ;--更新emp表中對應員工編號的工資
  if sql%notfound then  
    raise no_result;
  end if; 
exception
  when no_result then 
    dbms_output.put_line('數據更新語句失敗');
  when others then 
    dbms_output.put_line('發生其他錯誤');
end;

        代碼分析:sql%notfound 是隱式遊標 SQL 的 notfound 屬性, 返回一個布爾值,表示與它最近的一條 SQL 語句(update,insert,delete,select)是否得到結果。當最近的一條sql語句沒有涉及任何行的時候,則返回true。否則返回false。這樣的語句在實際應用中,是非常有用的。例如要update一行數據時,如果沒有找到,就可以作相應操作。

 

2、  異常作用範圍

        通過一個案例來探討異常處理的作用範圍。

--異常作用範圍理解,案例1
declare
  v_test char(3) ;--聲明一個變量v_test,數據類型是char,長度是3
begin
  <<inner_block>>--內層語句塊
    begin
      v_test := 1234 ; 
      dbms_output.put_line('這只是一個測試');
    exception
      when invalid_number or value_error then 
        dbms_output.put_line('內層捕獲錯誤');
  end inner_block;
exception
  when invalid_number or value_error then 
    dbms_output.put_line('外層捕獲錯誤');
end;

        示例中,我們使用了嵌套語句塊結構。在內層塊中,我們把數字1234賦值給字符型變量從而產生錯誤(超出允許長度)。於是該錯誤被內層塊中異常處理語句捕捉並處理。

執行結果:

對上一個例子我們稍作改動,如果內層語句塊中沒有異常處理語句,並且錯誤發生在內層語句塊,那麼該錯誤由誰來處理呢?

--異常作用範圍理解,案例2
declare
  v_test char(3) ;--聲明一個變量v_test,數據類型是char,長度是3
begin
  <<inner_block>>
    begin
      v_test := 1234; 
      dbms_output.put_line('這只是一個測試');
  end inner_block;--內層沒有異常處理語句,直接結束內層語句塊
exception
  when invalid_number or value_error then 
    dbms_output.put_line('外層捕獲錯誤');
end;

執行結果:

        示例中,由於內層語句塊出現錯誤,但是內層語句塊沒有異常處理語句,所以,該錯誤被外層語句塊的異常處理語句捕獲,並處理。

總結:如果在語句塊中定義一個異常處理語句,那麼該異常處理語句只捕獲處理當前所在的語句塊中出現的錯誤。在嵌套語句塊結構中,內層語句塊無法捕捉的錯誤,可以被外層語句塊的異常處理語句捕捉並處理。

 

3、  異常傳播

        當 PL/SQL 語句塊的可執行部分出現某個運行時錯誤時,會拋出不同類型的異常。但是,運行時錯誤也可能發生在語句塊的聲明部分或者異常處理部分。控制在這些環境下異常拋出方式的規則被稱爲異常傳播。

3.1  聲明部分異常

        通過案例來理解,當語句塊的聲明部分發生運行時錯誤時,異常是如何傳播的。

(1)例如:聲明一個變量,給其賦一個錯誤值。

--案例1、聲明部分異常
declare
  v_test char(3) := 'ABCDE' ;--聲明一個長度是3的字符型變量,同時賦值,賦值超出長度
begin
  dbms_output.put_line('這只是一個測試');
exception
  when invalid_number or value_error then 
    dbms_output.put_line('異常發生');
end ;

輸出結果:

執行結果中,並沒有輸出異常發生,而是系統提示出錯。

語法說明:語句塊中定義了異常處理語句,可當聲明部分出現異常時,並沒有觸發其本身定義的異常處理語句,而是交給了系統處理。我們可以下一個結論:當 PL/SQL 語句塊的聲明部分出現運行時錯誤時,該語句塊的異常處理部分不能捕獲此項錯誤

 

(2)將上個例子稍作修改:

本示例在上個示例語句塊的外層嵌套了一個外層語句塊,並定義了 EXCEPTION 結構。 於是本示例的執行結果爲:

因此,我們可以得出結論:當內部語句塊的聲明部分發生運行時錯誤時,該異常會立即傳播到外部語句塊

 

3.2異常處理部分異常

        通過分析知道,當聲明部分出現錯誤時,與聲明部分同級的異常處理部分不會處理該錯誤,而是將該錯誤傳播到外層語句塊的異常處理部分處理。那麼如果異常處理部分內部又產生了錯誤,那麼該錯誤又該如何處理呢?

我們來看個例子:

--案例2、異常處理部分異常
declare
    v_test char(3) := 'ABC' ;
begin
     v_test := 1234 ;--把數字賦值給字符型變量,且超出長度
     dbms_output.put_line('這只是一個測試');
exception
    when invalid_number or value_error then 
      v_test := 'ABCDE' ; --賦值超出長度
      dbms_output.put_line('異常發生');
end;

執行結果中,並沒有輸出異常發生,而是系統提示出錯。

        案例分析:在執行部分,將 1234 賦值給一個字符變量,會出現錯誤,於是被本語句塊中的 EXCEPTION 部分捕捉,可是進行異常處理時,將‘ABCDE’給變量賦值,超出了變量類型的範圍,又產生了錯誤。最終結果,異常處理部分並沒有同時處理這兩次錯誤,而是拋給了系統處理。我們可以下一個結論:PL/SQL 語句塊的異常處理部分發生運行時錯誤時,該語句塊的異常處理部分不能防止這個錯誤

如果我們使用嵌套 PL/SQL 語句塊:

輸出結果:

本案例完整代碼:

--案例2-1、異常處理部分異常(嵌套)
begin
    <<inner_block>>
    declare
        v_test char(3) := 'ABC' ;
    begin
         v_test := 1234 ;--把數字賦值給字符型變量,且超出長度
         dbms_output.put_line('這只是一個測試');
    exception
        when invalid_number or value_error then 
          v_test := 'ABCDE' ; --賦值超出長度
          dbms_output.put_line('異常發生--內層錯誤被捕獲');
    end inner_block;
exception
    when invalid_number or value_error then 
       dbms_output.put_line('異常發生--外層錯誤被捕獲'); 
end;

通過本示例,我們可以得出結論:當內部語句塊的異常處理部分發生運行時錯誤時,該異常會立即傳播到外部語句塊

 

3.3顯式拋出異常

       前面兩種異常處理方式屬於隱式拋出異常,即程序自動向外層拋出異常。那麼也可以根據需要,使用 RAISE 語句,人爲地控制異常的拋出,我們稱之爲顯式拋出異常。

例如:

--案例3、顯示拋出異常
declare
    e_exception1 exception;
    e_exception2 exception;
begin
    <<inner_block>>
      begin
           raise e_exception1;
      exception
          when e_exception1 then             
             raise e_exception2;
          when e_exception2 then             
             dbms_output.put_line('內層捕獲e_exception2錯誤');
    end inner_block;
exception
    when e_exception2 then 
       dbms_output.put_line('外層捕獲e_exception2錯誤'); 
end;

輸出結果:

       示例中,聲明瞭兩個異常:e_exception1 e_exception2 。在內部語句塊中,使用 RAISE 語句拋出 e_exception1 異常。在該語句塊中異常處理部分,異常 e_exception1 會嘗試拋出 e_exception2 異常。儘管內部語句塊中存在異常 e_exception2 的異常處理程序,但是執行 權卻轉到了外層語句塊。之所以發生這種情況,原因在於,異常處理部分一次只會處理其中一個異常,不能一次處理多個異常

 

3.4再次拋出異常

        在有些情況下,當發生特定類型的錯誤時,也許希望內部語句塊異常處理語句對該錯誤不做任何處理,而是希望將錯誤拋給外層語句塊去處理,那麼這個過程,我們稱之爲再次拋出異常。

例如:

--案例4、再次拋出異常
declare
    e_exception exception;
begin
    <<inner_block>>
      begin
           raise e_exception;
      exception
          when e_exception then             
             raise;
    end inner_block;
exception
    when e_exception then 
       dbms_output.put_line('捕獲再次拋出的異常'); 
end;

輸出結果:

       在這個示例中,異常 e_execption 被聲明在外部語句塊中。然後,在內部語句塊中拋出它。這樣的話,執行權會轉到內部語句塊的異常處理部分。該語句塊的異常處理部分的 RAISE 語句會導致該異常傳播到外部語句塊的異常處理部分。

 

 

 

 

==========這裏是結束分割線=============

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章