当前位置:   article > 正文

PLSQL的异常传播

pgsql 执行存储过程during statement block local variable initialization

文章概要:

本文对主要就PLSQL的异常传播进行知识性小结,分为四个部分,PLSQL异常传播小结,编写小案例验证5种传播规则,示例了一个容易理解出错的案例,以及使用goto结合异常处理的案例。

一,异常传播规则

PLSQL块结构,典型如下:

  1. declare
  2. --声明区域
  3. begin
  4. --执行区域
  5. exception
  6. --异常处理区域
  7. end

上述三个区域都可以产生异常(PLSQL自动抛出来的,或者代码主动抛出来的异常),PL/SQL采用统一异常处理机制,当异常发生时,程序会自动跳转到异常处理区域,交给异常处理程序进行异常匹配,
处理完异常后,程序的控制流程继续向外部传递。
就传播规则而言,分两大情况,5小情况。

1,执行区域产生异常时,异常传播方式分为下三种情况:
1)如果当前语句块有该异常的处理器,则程序流程转移到该异常处理器,并进行异常处理。然后,程序的控制流程传递到外层语句块,继续执行。
2)如果当前语句块没有该异常处理器,则在外层语句块的异常处理部分处理该异常。处理完异常后,程序的控制流程继续向外部传递。
3)如果当前语句块及其外层语句块都没有对该异常的处理,则该异常将传播到调用环境(比如ksql客户端)或主机环境。
简而言之,异常会向他的当前子块传播,不能被捕获,则向外层进行传递,

2,声明区域和异常处理区域产生的异常传播策略有明显不同:
会立刻传播到外层语句块的异常处理部分, 即使当前语句块有该异常的异常处理器也不会进行捕获处理。
4)如果外层语句块无法处理该异常,则异常继续向更外层传播,直到调用环境或主机环境。
5)当外层语句块捕获并处理内层块的异常后,程序流程继续 向外层传递并执行。
任何情况(指1-5五种情况),直到异常被捕获则终止或最终到调用环境或主机环境。

二,异常传播实例

用尽可能简单的例子对上述5个情况进行实测:1)如果当前语句块有该异常的处理器,则程序流程转移到该异常处理器,并进行异常处理。然后,程序的控制流程传递到外层语句块,继续执行。
--1

  1. begin
  2. select 1/0 as result;
  3. exception
  4. when others then
  5. raise notice '当前块捕获到异常';
  6. end;
  7. --运行结果
  8. NOTICE: 当前块捕获到异常
  9. ANONYMOUS BLOCK

--2

  1. begin
  2. begin
  3. select 1/0 as result;
  4. exception
  5. when others then
  6. raise notice '内块捕获到异常';
  7. end;
  8. raise notice '继续执行程序';
  9. exception
  10. when others then
  11. raise notice '外块捕获到异常';
  12. end;
  13. --运行结果
  14. NOTICE: 内块捕获到异常
  15. NOTICE: 继续执行程序
  16. ANONYMOUS BLOCK

2)如果当前语句块没有该异常处理器,则在外层语句块的异常处理部分处理该异常。处理完异常后,程序的控制流程继续向外部传递。

  1. begin
  2. begin
  3. begin
  4. select 1/0 as result;
  5. --exception -- 注释掉异常处理块
  6. -- when others then
  7. -- raise notice '内块捕获到异常';
  8. end;
  9. exception
  10. when others then
  11. raise notice '外块捕获到异常';
  12. end;
  13. raise notice '继续执行程序';
  14. end;
  15. --运行结构
  16. NOTICE: 外块捕获到异常
  17. NOTICE: 继续执行程序
  18. ANONYMOUS BLOCK

3)如果当前语句块及其外层语句块都没有对该异常的处理,则该异常将传播到调用环境(比如ksql客户端)或主机环境。

  1. begin
  2. select 1/0 as result;
  3. end;
  4. --运行结果
  5. ERROR: division by zero
  6. CONTEXT: SQL statement "select 1/0 as result"
  7. PL/SQL function inline_code_block line 2 at SQL statement

**4)如果外层语句块无法处理该异常,则异常继续向更外层传播,直到调用环境或主机环境。 **
--1

  1. declare
  2. vv CONSTANT NUMBER(2):=500; ---在本地声明部声明常量,numeric field overflow
  3. begin
  4. raise notice '变量vv值为:%',vv;
  5. exception
  6. when others then
  7. raise notice '最外层块捕获到异常';
  8. end;
  9. --运行结果
  10. ERROR: numeric field overflow
  11. DETAIL: A field with precision 2, scale 0 must round to an absolute value less than 10^2.
  12. CONTEXT: PL/SQL function inline_code_block line 3 during statement block local variable initialization

--2

  1. declare
  2. declare
  3. vv CONSTANT NUMBER(2):=500; ---在本地声明部声明常量,numeric field overflow
  4. begin
  5. raise notice '变量vv值为:%',vv;
  6. exception
  7. when others then
  8. raise notice '声明区域捕获到异常';
  9. end;
  10. begin
  11. raise notice '继续执行程序';
  12. exception
  13. when others then
  14. raise notice '外层块捕获到异常';
  15. end;
  16. --运行结果
  17. ERROR: numeric field overflow
  18. DETAIL: A field with precision 2, scale 0 must round to an absolute value less than 10^2.
  19. CONTEXT: PL/SQL function inline_code_block line 4 during statement block local variable initialization

--3

  1. begin
  2. declare
  3. vv CONSTANT NUMBER(2):=500; ---在本地声明部声明常量,numeric field overflow
  4. begin
  5. raise notice '变量vv值为:%',vv;
  6. exception
  7. when others then
  8. raise notice '声明区域捕获到异常';
  9. end;
  10. raise notice '继续执行程序';
  11. exception
  12. when others then
  13. raise notice '外层块捕获到异常';
  14. end;
  15. --运行结果:
  16. NOTICE: 外层块捕获到异常
  17. ANONYMOUS BLOCK

5)当外层语句块捕获并处理内层块的异常后,程序流程继续向外层传递并执行。

  1. begin
  2. begin
  3. declare
  4. vv CONSTANT NUMBER(2):=500; ---在本地声明部声明常量,numeric field overflow
  5. begin
  6. raise notice '变量vv值为:%',vv;
  7. exception
  8. when others then
  9. raise notice '声明区域捕获到异常';
  10. end;
  11. raise notice '继续执行程序1';
  12. exception
  13. when others then
  14. raise notice '外层块捕获到异常';
  15. end;
  16. raise notice '继续执行程序2';
  17. end;
  18. --运行结果
  19. NOTICE: 外层块捕获到异常
  20. NOTICE: 继续执行程序2
  21. ANONYMOUS BLOCK

三,一个易理解错误的例子

再来看一个容易理解出错的案例:

  1. declare
  2. --声明区域
  3. function func_test () return int
  4. as
  5. declare
  6. a int;
  7. begin
  8. a = 10;
  9. select a/0;
  10. return 1;
  11. exception
  12. when others then
  13. raise notice '函数内捕获到异常';
  14. end;
  15. begin
  16. raise notice '测试';
  17. select func_test();
  18. exception
  19. when others then
  20. raise notice '块外捕获到异常';
  21. end
  22. --运行结果:
  23. NOTICE: 测试
  24. NOTICE: 函数内捕获到异常
  25. NOTICE: 块外捕获到异常
  26. func_test
  27. -----------
  28. (0 rows)

这个案例在函数内和快外都捕捉到了异常,难道异常传播了两次?实际不是,看下面这个例子一目了然

  1. declare
  2. --声明区域
  3. function func_test () return int
  4. as
  5. declare
  6. a int;
  7. begin
  8. a = 10;
  9. select a/0;
  10. return 1;
  11. exception
  12. when zero_divide then
  13. raise notice '函数内捕获到异常--》%',sqlerrm;
  14. end;
  15. begin
  16. raise notice '测试';
  17. select func_test();
  18. exception
  19. when others then
  20. raise notice '块外捕获到异常--》%',sqlerrm;
  21. end
  22. --运行结果
  23. NOTICE: 测试
  24. NOTICE: 函数内捕获到异常--》division by zero
  25. NOTICE: 块外捕获到异常--》control reached end of function without RETURN
  26. func_test
  27. -----------
  28. (0 rows)

到此实际上已经真相大白,是因为嵌套函数func_test的exception没有返回值造成。
这个例子从侧面说明了,不管何时,尽量通过异常名称捕获异常,针对特定的错误进行处理,尽量少使用OTHERS异常处理器。
但在最外层块的异常处理部分放置OTHERS异常处理器,避免有未被处理的异常,是没有问题的。

四,GOTO结合异常处理

如前文所说PL/SQL采用统一异常处理机制,当异常发生时,程序会自动跳转到异常处理区域,交给异常处理程序进行异常匹配,
处理完异常后,程序的控制流程继续向外部传递顺序执行下去。
如果在处理完异常后,修复了异常后,我们不希望异常向外部传递后顺序执行下去呢?我们可以用GOTO进行跳转。
GOTO语句不能跳转到异常控制程序。同样,GOTO语句也不能从异常控制程序跳转到当前块。
例如,下面的GOTO语句就是非法的:

  1. declare
  2. a int;
  3. begin
  4. a = 0;
  5. <<zero_divide_label>>
  6. a = 10/a;
  7. raise notice '测试结果:%',a;
  8. exception
  9. when zero_divide then
  10. raise notice '块外捕获到异常--》%',sqlerrm;
  11. a = 1;
  12. GOTO zero_divide_label; ----非法的跳转到当前块
  13. end
  14. --运行结果
  15. ERROR: illegal GOTO statement, cannot transfer control to label 'zero_divide_label'
  16. CONTEXT: compilation of PL/SQL function "inline_code_block" near line 3

但是,GOTO语句可以从一个异常控制程序中跳转到一个封闭块,上述代码调整为:

修复除零异常后重新执行原预期代码

  1. declare
  2. a int;
  3. begin
  4. a = 0;
  5. <<zero_divide_label>>
  6. begin
  7. a = 10/a;
  8. raise notice '测试结果:%',a;
  9. exception
  10. when zero_divide then
  11. raise notice '块外捕获到异常--》%',sqlerrm;
  12. a = 1;
  13. GOTO zero_divide_label; ----合法的跳转到当前块
  14. end;
  15. end
  16. --运行结果
  17. NOTICE: 块外捕获到异常--》division by zero
  18. NOTICE: 测试结果:10
  19. ANONYMOUS BLOCK
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/417258
推荐阅读
相关标签
  

闽ICP备14008679号