570 likes | 684 Views
第九章 异常处理. 9.1 什么是异常情态 异常处理处理的是运行时错误,异常分为预定义异常和用户自定义异常。 声明异常情态 异常情态在块的声明部分进行声明,在块的执行部分进行触发,在块的异常部分进行处理。 用户定义类型异常情态 例: Declare e_toomanystudents exception;. e_toomanystudents 的作用域和本块的其他变量相同。 预定义型异常情态 预定义型异常情态可以直接使用,没有必要声明。 Invalid_cursor: 当执行非法的游标操作时,会引发这个错误,如试图关闭已关闭的游标。
E N D
第九章 异常处理 • 9.1 什么是异常情态 • 异常处理处理的是运行时错误,异常分为预定义异常和用户自定义异常。 • 声明异常情态 • 异常情态在块的声明部分进行声明,在块的执行部分进行触发,在块的异常部分进行处理。 • 用户定义类型异常情态 • 例: • Declare • e_toomanystudents exception;
e_toomanystudents的作用域和本块的其他变量相同。e_toomanystudents的作用域和本块的其他变量相同。 • 预定义型异常情态 • 预定义型异常情态可以直接使用,没有必要声明。 • Invalid_cursor:当执行非法的游标操作时,会引发这个错误,如试图关闭已关闭的游标。 • Cursor_already_open:试图打开已经打开的游标,会引发这个错误。 • No_data_found:当select into语句没有返回行时,和引用没有赋值过的pl/sql表的元素时会引发这个错误。 • Too_may_rows:当select into 语句返回多个行时,会引发这个错误。 • Invalid_number:当试图从字符串转换为数值失败时,会引发这个错误,在过程性语句中会引发value_error错误。 • 如:下面的语句引发invalid_number错误,因为‘x’不是数值。 • Insert into students(id,first_name,last_name) • values (‘x’,’scott’,’smith’);
Storage_error和program_error:是内部的异常情态,通常不会引发他们。Storage_error和program_error:是内部的异常情态,通常不会引发他们。 • Value_error:当在过程性语句中发生了算术、转换、截尾或限制性错误时会引发这个情态。如果在sql语句中发生错误,会引发invalid_number错误。这个错误可能是一条赋值语句或select into语句的执行结果。下面的两个例子都引发value_error错误。 • Declare • v_tempvar varchar2(3); • Begin • v_tempvar:='ABCD'; • End; • Declare • v_tempvar number(2); • Begin • select id into v_tempvar from students • where last_name='smith'; • End;
Rowtype_mismatch:当宿主变量和pl/sql游标变量不匹配时,会引发这个异常情态。Rowtype_mismatch:当宿主变量和pl/sql游标变量不匹配时,会引发这个异常情态。 • 触发异常情态 • 当与预定义的异常情态错误发生时,就会触发该异常情态。用户定义的异常情态由raise语句触发,当然,预定义的异常情态也可以用raise引发。 • 例: • Declare • e_toomanystudents exception • v_currentstudents number(3); • v_maxstudents number(3); • Begin • select current_students,max_students • into v_currentstudents,v_maxstudents • from classes • where department=‘HIS’ and course=‘101’;
if v_currentstudents>v_maxstudents then • raise e_toomanystudents • End if; • End; • 当引发异常情态时,控制转给该块的异常处理部分。如果没有异常处理部分,该异常处理部分将传给包含该块的块。一旦将控制转给异常处理部分,没有办法再将控制返回给该块的执行部分。 • 处理异常情态 • 异常部分的语法如下: • Exception • when exception_name then • 处理错误语句序列 • when exception_name then • 处理错误语句序列 • when others then • 处理错误语句序列 • end;
一个处理器可以对多个异常情态进行处理,用or连接一个处理器可以对多个异常情态进行处理,用or连接 • 例: • Exception when no_data_found or too_many_rows then • insert into log_table(info) values (‘a select error occurred’); • End; • Others异常处理器将对所有语法的异常情态进行处理,一般放在异常处理的最后,可以保证所有的错误都被检测到。 • Declare • e_toomanystudents exception • v_currentstudents number(3); • v_maxstudents number(3); • v_errorcode number; • v_errortext varchar2(200); • Begin • select current_students,max_students • into v_currentstudents,v_maxstudents • from classes • where department=‘HIS’ and course=‘101’;
if v_currentstudents>v_maxstudents then • raise e_toomanystudents • End if; • Exception • when e_toomanystudents then • insert into log_table(info) • values (‘history 101 has’ || v_currentstudents || • ‘ students: max allowed is ‘ || v_maxstudents); • when others then • v_errorcode:=sqlcode; • v_errortext:=substr(sqlerrm,1,200); • insert into log_table(info) • values (‘another error occurred’); • end; • *sqlcode和sqlerrm先被赋值给本地变量,然后这些变量在sql语句中被使用,sqlcode和sqlerrm不能直接在sql语句中使用,因为他们是过程性的函数。sqlcode返回当前的错误号,sqlerrm返回当前的错误信息正文。
Sqlerrm也可以单个的数值参数进行调用,他返回与数值相关的消息正文,此参数应该是负值。Sqlerrm也可以单个的数值参数进行调用,他返回与数值相关的消息正文,此参数应该是负值。 • Sqlerrm(0): ora-0000:normal,successful completion • sqlerrm(100): ora-1403:no data found • 100以外的正值调用,返回:user-defined exception • 从执行部分调用sqlerrm(不带参数),返回:ora-0000:normal,successful completion • 并且sqlcode返回0 • 当从异常处理器中调用时,sqlcode返回一个指示错误号的负值。唯一的例外是错误: ora-1403:no data found,sqlcode返回+100. • 例:
Declare • v_errortext log_table.message%type; • Begin • v_errortext:=substr(sqlerrm(0),1,200); • insert into log_table(code,message,info) • values(0,v_errortext,’sqlerrm(0)’); • v_errortext:=substr(sqlerrm(100),1,200); • insert into log_table(code,message,info) • values(100,v_errortext,’sqlerrm(100)’); • v_errortext:=substr(sqlerrm(10),1,200); • insert into log_table(code,message,info) • values(10,v_errortext,’sqlerrm(10)’);
v_errortext:=substr(sqlerrm,1,200); • insert into log_table(code,message,info) • values(null,v_errortext,’sqlerrm’); • v_errortext:=substr(sqlerrm(-1),1,200); • insert into log_table(code,message,info) • values(-1,v_errortext,’sqlerrm(-1)’); • v_errortext:=substr(sqlerrm(-54),1,200); • insert into log_table(code,message,info) • values(-54,v_errortext,’sqlerrm(-54)’); • End; • 运行以上程序结果,log_table表中的内容为:
Exception_init • 可以将一个经过命名的异常情态与一个特别的oracle错误相联系。这样可以用when扑获此错误,不用others扑获。通过exception_init pragma实现的。其语法如下: • Pragma exception_init (exception_name,oracle_error_number) • Pragma必须在声明部分 • 例:下面的例子在运行时刻如遇到“ora-1400:mandatory not • null column missing or null during insert”错误时,将引发e_missingnull异常情态。 • Declare • e_missingnull exception; • pragma exception_init (e_missingnull,-1400); • Begin • insert into students(id) values(null); • Exception • when e_missingnull then • insert into log_table(info) values(‘ora-1400 occurred’); • End;
每次发生pragma exception_init时,一个oracle错误只能和一个用户定义的异常情态相关联。在异常处理器内部,sqlcode和sqlerrm将返回发生oracle错误的代码和错误信息,而不会返回用户定义的消息。 • 例: • Declare • ex_hfy exception; • Pragma exception_init(ex_hfy,-1400); • Begin • insert into students values(null); • Exception • when ex_hfy then • dbms_output.put_line('不能把空值插入到非空列'); • End; • 注意,这里-1400不能为别的,因为把空值插入到非空列的错误号就是这个。可以单独执行insert语句查看错误号。
使用raise_application_error • 可以用raise_application_error创建自己的错误消息,这比命名的异常情态更具有说明性。其语法如下: • Raise_application_error(error_number,error_message,[keep-errors]); • 这里error_number是从-20,000到-20,999之间的数,error_message是与此错误相关的错误正文。Keep_errors为布尔值,如果他为true,则新的错误被添加到已经引发的错误列表中(如果有的话)。如果为false(为缺省值),则新的错误将替换错误的当前列表。例:下面的过程为一个新的学生注册以前,检查是否在班级中有足够的地方容纳他。
Create or replace procedure register( • p_studentid in students.id%type, • p_department in classes.department%type, • p_course in classes.course%type) as • v_currentstudents number; • v_maxstudents number; • Begin • select current_students,max_students • into v_currentstudent,v_maxstudents • from classes • where department=p_department • and course=p_course;
if v_currentstudents+1>v_maxstudents then • raise_application_error(-20000, • ‘can’’t add more students to’ || • p_department || ‘ ‘ ||p_course); • end if; • classpackage.addstudent(p_studentid, • p_department,p_course); • Exception • when no_data_found then • raise_application_error(-20001,p_department || • ‘ ‘ || p_course || ‘doesn’’t exist!’); • End register; • 以上程序运行时,当没有足够的空间容纳新的学生时,返回ora-20000: can’t add more students to his 101错误消息。
异常情态传播 • 异常情态可以出现在块的声明部分、执行部分或异常情态部分。对于执行部分出现异常时,会定义一个异常处理器。但是没有异常处理器,或从块的不同部分引发该异常情态时,会发生什么?控制这种情况的处理就是异常情态传播。 • 在执行部分引发的异常情态 • 当在一个执行部分引发一个异常情态时,oracle根据以下算法决定激活哪个异常处理器。 • 如果当前块对该异常情态设置了处理器,则执行他,并成功完成该块的执行,然后控制会转给包含块。 • 如果没有对当前异常情态定义处理器,则通过在包含块 中引发他来传播异常情态,然后对包含块执行步骤1。
外部块的定义:一个块可以嵌套在另一个块中,此时外部的块叫包含块。在下面的例子中块1和块2没有处理的异常嵌套将被传播到外部块中。外部块的定义:一个块可以嵌套在另一个块中,此时外部的块叫包含块。在下面的例子中块1和块2没有处理的异常嵌套将被传播到外部块中。 • Declare • … • --outer block • Begin • … • declare • --begin inner block 1 • … • begin • … • end;
begin • --inner block 2 • … • end; • --end outer block • End; • 过程调用也产生外部块。例: • Begin • --调用的过程f将被这个外部块包含 • f(… ); • End;
传播示例1 • 说明了规则1的应用。异常嵌套a被引发并在子块中被处理,然后控制转给外部块。 Declare A exception ; begin Begin Raise a Exception when a then … End; End;
传播示例2 • 对子块引用了规则2,该异常情态被传播到包含块。 Declare A exception; B exception; begin Begin Raise B Exception when a then … End; Exception When b then … End;
传播示例3: • 对子块应用了规则2,该异常情态被传播给包含块,但是在包含块中仍然没有相应的处理器,于是再次应用了规则2。包含块由于没有处理异常情态而没有成功完成处理。 • 在说明部分引发的异常情态 • 如果在声明部分的赋值操作引发了一个异常情态 ,该异常情态立即传播给包含块。尽管在当前块中有一个处理器,他也不会被执行。 • 1.传播示例4: • 在这个例子中value_error由下面的语句引发的。 • V_number number(3):=‘abc’; • 这个异常情态立即被传播给包含块,尽管有一个others异常情态,也不会去执行他。
begin Declare v_number number:=‘abc’; Begin … Exception When others then End; Exception When others then End;
在异常处理部分引发的异常情态s • 在异常处理器中,引发异常情态可以通过raise语句显式引发,也可以由运行时刻错误隐含引发的,无论如何,异常情态立即传播到包含块。在异常处理部分,一次只能有一个异常情态是活动的。只要一个异常处理器被处理了,另一个异常情态就可以被引发,但是不能有两个异常情态被同时引发。 • 在异常处理器中,可以不带参数使用raise,此时当前的异常情态就被传播到包含块 中。这个技术用来记录错误
begin • Declare • a exception; • b exception; • Begin • raise a; • Exception • when a then • raise b; • when b then • … • End; Exception when b then End;
异常处理指南 • 异常情态的作用域 • 异常情态的作用域和变量的类似,如果用户定义的异常情态传播到了其作用域之外,就不能再通过名字引用他了。 • 例: • Declare • … • Begin • … • declare • e_userdefinedexception exception; • begin • raise e_userdefinedexception; • end;
Exception • --此时不能用e_usedefinedexception引用他,只能在----others中处理 • when others then • … • End; • 通常,如果一个用户定义的异常情态被传播到块的外面,最好是在包中定义块的异常情态,这样在块的外面,该异常情态也是可见的。或者使用raise_application_error。 • 例: • Create or replace package globals • e_userdefinedexception exception; • End globals; • 在定义了globals包以后,可以改写上面的程序。
Declare • … • Begin • … • begin • raise e_userdefinedexception; • end; • Exception • when globals.e_userdefinedexception then • … • when others then • … • End;
避免未处理的异常情态 • 避免未处理的异常情态是一个很好的编程风格。通过在程序的最顶层使用others处理器来做到。这个处理器可能只是记录下发生的错误和发生错误的位置。这样,可以确保没有错误逃过检测。例: • Declare • v_errornumber number; • v_errortext varchar2(200); • Begin • … • Exception • … • when others then • v_errornumber:=sqlcode; • v_errortext:=substr(sqlerrm1,200);
insert into log_table(code,message,info) values • (v_errornumber,v_errortext,’oracle error occurred at’ || • to_char(sysdate,’DD-MON-YY HH24:MI:SS’)); • END; • 屏蔽错误发生的位置 • 因为整个块所检测的是同一个异常情态部分,所以要确定是哪个sql语句导致了该错误是非常困难的。 • 例: • Begin • select… • select… • select… • Exception • when no_data_found then • … • End;
有两种方法解决这个问题。一是对一个计数器执行加一操作来标识被执行的sql语句。例:有两种方法解决这个问题。一是对一个计数器执行加一操作来标识被执行的sql语句。例: • Declare • v_selectcounter number:=1; • Begin • select.. • v_selectcounter:=2; • select… • v_selectcounter:=3; • select… • Exception • when no_data_found then • insert into log_table((info) values • (‘no data found in select’ || v_selectcounter); • End;
第二种方法是将每条语句放入自己的子块中 • Begin • begin • select … • exception • when no_data_found then • insert into log_table(info) values • (‘no data found in select 1’); • end; • begin • select … • exception • when no_data_found then • insert into log_table(info) values • (‘no data found in select 2’); • end; • End;
使用pl/sql:常用错误处理模块 • Pl/sql的dbms_utility.format_call_stack函数返回当前的调用堆栈,返回值是varchar2类型。下面的例子a调用b,b调用c。 • Create or replace procedure c as • v_callstack varchar2(2000); • Begin • v_callstack:=dbms_utility.format_call_stack; • insert into temp_table(char_col) values (v_callstack); • insert into temp_table(num_col) values (-1); • End c; • Create or replace procedure b as • Begin • c; • End b;
Create or replace procedure a as • b • End a; • 从temp_table中进行选择得到如下结果: • ----Pl/sql call stack--- • Object line object • Handle number name • 1699810 4 procedure example.c • 1699ca0 3 procedure example.b • 1691918 3 procedure example.a • 1667ef0 1 anonymous block • Dbms_utility.format_error_stack将返回当前的错误序列。有了这两个函数,就可以编写记录发生的错误位置和发生的是什么错误的通用错误处理器了。
下面是这个包要用到的表定义 • Create table errors( • module varchar2(50), • seq_number number, • error_number number, • error_mesg varchar2(100), • error_stack varchar2(2000), • call_stack varchar2(2000), • timestamp date, • primary key(module,seq_number));
Create table call_stacks( • module varchar2(50), • seq_number number, • call_order number, • object_handle varchar2(10), • line_num number, • object_name varchar2(80), • primary key(module,seq_number,call_order), • foreign key(module,seq_number) references errors on delete cascade); • Drop table error_stacks;
Create table error_stacks( • module varchar2(50), • seq_number number, • error_order number, • facility char(3), • error_number number(5), • error_mesg varchar2(100), • primary key(module,seq_number,error_order), • foreign key(module,seq_number) references errors on delete cascade); • Create sequence error_seq • start with 1 • increment by 1; • 下面给出errorpkg包的定义 • --这个包存储一般的错误信息在errors表中,分别存储详细的调用堆栈和错误堆栈在call_stacks和erors_stacks表中。
Create or replace errorpkg as • --handleall应该被所有的想记录错误的例外处理器调用,---当最顶层的过程调用时,p_top参数为true,否则为false。 • procedure handleall(p_top boolean); • --对于给定的module和seq_number,用dbms_output打印 • --错误和调用堆栈 • procedure printstacks (p_module in errors.module%type, • p_seqnum in errors.seq_number%type); • /*展开调用和错误堆栈,将他们存储在errors和call_stacks • 表中,对于被存储的错误,返回seq_number。如果p_commitflag标志为true,则插入被提交。为了使用storestacks,错误必须已被处理过,这样,handleall应该用p_top=true调用。*/
procedure storestacks(p_module errors.module%type, • p_seqnum errors.seq_number%type, • p_commitflag boolean default false); • End errorpkg; • Create or replace package body errorpkg as • v_newline constant char(1):=char(10); • v_handled boolean:=false; • v_errorstack varchar2(2000); • v_callstack varchar2(2000) ;
Procedure handleall(p_top in boolean) is • Begin • if p_top then • v_handled:=false; • elsif not v_handled then • v_handled:=true; • v_errorstack:=dbms_utility.format_error_stack; • v_callstack:=dbms_utility.format_call_stack; • end if; • End handleall; • Procedure printstack(p_module in errors.module%type, • p_seqnum in errors.seq_number%type) is • v_timestamp errors.timestamp%type; • v_errormsg errors.error_mesg%type;
cursor c_callcur is • select object_handle,lin_num,object_name • from call_stacks • where module=p_module and seq_number=p_seqnum • order by call_order; • cursor c_errorcur is • select facility,error_number,error_mesg • from error_stacks • where module=p_module and seq_number=p_seqnum • order by error_order; • Begin • select timestamp,error_mesg • into v_timestamp,v_errormsg • from errors • where module=p_module and seq_number=p_seqnum;
--输出一般的错误信息 • dbms_output.put(to_char(v_timestamp, • DD-MON-YY HH24:MI:SS’)); • dbms_output.put(‘module:’ || p_module); • dbms_output.put(‘error#’ || p_seqnum || ‘:’); • dbms_output.put_line(v_errormsg); • --输出调用堆栈 • Dbms_output.put_line(‘complete call stack:’); • Dbms_output.put_line(‘object handle line number object name’); • Dbms_output.put_line(---------------------------------------------’); • For v_callrec in c_callcur loop • dbms_output.put(rpad(‘ ‘ || v_callrec.object_handle,15)); • dbms_output.put(rpad(‘ ‘ ||v_callrec.linenum,13)); • dbms_output.put_line(‘ ’ ||v_callrec.object_name); • End loop;
--输出错误堆栈 • Dbms_output.put_line(‘complete error stack:’); • For v_errorrec in c_errorcur loop • dbms_output.put(‘ ‘ || v_errorrec.facility || ‘-’); • dbms_output.put(to_char(v_errorrec.error_number) || ‘:’); • dbms_output.put_line(v_errorrec.error_mesg); • End loop; • End printstacks; • procedure storestacks(p_module in errors.module%type, • p_seqnum out errors.seq_number%type, • p_commitflag boolean default false) is • v_seqnum number; • v_index number; • v_length number; • v_end number; • v_call varchar2(100);
v_callorder number:=1; • v_handle call_stacks.object_handle%type; • v_linenum call_stacks.line_num%type; • v_object_name call_stacks.object_name%type; • v_error varchar2(120); • v_errororder number:=1; • v_facility error_stacks.facility%type; • v_errnum error_stacks.error_number%type; • v_errmsg error_stacks.error_mesg%type; • v_firsterrnum error.error_number%type; • v_firsterrmsg error.error_mesg%type; • Begin • select error_seq.nextval • into v_seqnum • from dual;
p_seqnum:=v_seqnum; • --将第一部分的头信息插入errors表中 • insert into errors( • module,seq_number,error_stack,call_stack,timestamp) • values • (p_module,v_seqnum,v_errorstack,v_callstack,sysdate); • --展开错误堆栈,取得每一个错误 • v_index:=1; • --循环查找字符串,发现新行,当出现新行时,错误结束 • while v_index <length(v_callstack) loop • --v_end是v_newline的位置
v_end:=instr(v_errorstack,v_newline,v_index); • --这样错误位于现在的index和新行之间 • v_error:=substr(v_errorstack,v_index,v_end-v_index); • --跳过现在的错误,找下一个错误的开始位置 • v_index:=v_index+length(v_error)+1; • --一个错误的格式类似于:‘facility-number:mesg’ • --facility是错误的前3个字符 • v_facility:=substr(v_error,1,3); • --除去facility和破折号,共4位 • v_error:=substr(v_error,5); • --取得错误号 • v_errnum:=to_number(substr(v_error,1, • instr(v_error,’:’,)-1); • --除去错误号、冒号和空格,共7位 • v_error:=substr(v_error,8); • --剩下的就是错误信息 • v_errmsg:=v_error;
insert into error_stacks • (module,seq_number,error_order,facility,error_number, • error_mesg) • Values • (p_module,p_seqnum,v_errororder,v_facility,v_errnum, • v_errmsg); • If v_errororder=1 then • v_firsterrnum:=v_errnum; • v_firsterrmsg:=v_facility || ‘-’ || to_number(v_errnum) • || ‘:’ || v_errmsg; • End if; • V_error:=v_errorder+1; • End loop;
--用message和code修改错误表 • Update errors • set error_number=v_firsterrnum, • error_mesg=v_fisrerrmsg • where module=p_module and seq_number=v_seqnum; • --展开调用堆栈。通过扫描调用堆栈字符串,取得每一个调用,第一个调用的开始位于name和新行之后 • v_index:=instr(v_callstack,’name’)+5; • --发现新行,找新的调用
while v_index<length(v_callstack) loop • --v_end是新行的开始位置 • v_end:=instr(v_callstack,v_newline,v_index); • --这样v_call位于现在的index和新行之间 • v_call:=substr(v_callstack,v_index,v_end-v_index); • --跳过现在的call,取得下一个call的开始位置 • v_index:=v_index+length(v_call)+1; • --在call内部,先是object handle,然后是line number,然后是------object name,之间用空格隔开。 • v_call:=ltrim(v_call); • --取得object handle • v_handle:=substr(v_call,1,instr(v_call,’ ‘)); • --从call中除去object handle和空白 • v_call:=substr(v_call,length(v_handle)+1); • v_call:=ltrim(v_call); • --现在取得line number • v_linenum:=to_number(substr(v_call,1,instr(v_call,’ ‘ )));
--除去line_number和空白 • V_call:=substr(v_call,length(v_linenum)+1); • V_call:=ltrim(v_call); • --现在剩下的就是object name • V_objectname:=v_call; • --插入所有的call到call_stacks表中,除对errorpkg的调用外 • If v_callorder>1 then • insert into call_stacks • (module,seq_number,call_order,object_handle,line_num, • object_name) • values • (p_module,v_seqnum,v_callorder,v_handle,v_linenum, • v_objectname); • end if;