news 2026/10/22 16:43:25

PL/SQL执行语句实战:赋值、条件、循环、游标与异常处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PL/SQL执行语句实战:赋值、条件、循环、游标与异常处理

1. 从一段“看起来没问题”的代码说起

很多人学 PL/SQL 的时候,前面变量声明、数据类型、%TYPE、%ROWTYPE 都学得挺顺,一到执行语句这块就开始出问题。我见过太多这样的情况:语法背得滚瓜烂熟,真到写一个稍微复杂点的业务逻辑,要么是循环里少写了个EXIT WHEN,要么是SELECT INTO没处理NO_DATA_FOUND,跑起来直接报错,排查半天找不到原因。

这篇笔记专门聊 PL/SQL 的执行语句部分。所谓执行语句,就是BEGIN ... END块里真正干活的那些代码——赋值、条件判断、循环、SQL 语句嵌入、异常处理,这些构成了 PL/SQL 程序的主体。变量声明得再漂亮,执行语句写不好,程序照样跑不起来。

适合谁看?如果你已经写过一些简单的 PL/SQL 块,知道DECLARE和BEGIN的区别,但对条件分支的写法、循环的控制、游标的使用还不太有把握,那这篇内容就是给你准备的。如果你是完全零基础,建议先把变量声明和基本语法过一遍再来看,不然有些地方可能会觉得跳跃。

下面我会按照实际写代码时的思路来展开:先讲整体结构怎么设计,再拆解每一类执行语句的细节和坑,然后给一个完整的实操案例,最后把常见报错和排查方法整理出来。内容比较长,建议收藏着慢慢看,遇到具体问题的时候翻出来对照。

2. 执行语句的整体设计与思路拆解

2.1 为什么执行语句是 PL/SQL 的核心

PL/SQL 的程序结构分三部分:声明部分(DECLARE)、执行部分(BEGIN ... END)、异常处理部分(EXCEPTION)。声明部分定义变量和类型,异常处理部分兜底错误,而执行部分才是真正实现业务逻辑的地方。

打个比方,声明部分像是你出门前准备的工具箱,异常处理像是保险绳,执行部分才是你真正干活的过程。工具箱再齐全、保险绳再结实,活干得不对,结果还是白搭。

执行语句主要包含这几类:

  • 赋值语句:给变量赋值,包括直接赋值和SELECT INTO赋值
  • 条件控制语句:IF-THEN-ELSIF-ELSE和CASE
  • 循环语句:LOOP、WHILE LOOP、FOR LOOP
  • SQL 语句:INSERT、UPDATE、DELETE、SELECT INTO、游标操作
  • 顺序控制语句:GOTO、NULL
  • 动态 SQL:EXECUTE IMMEDIATE

这几类语句的组合方式决定了程序的逻辑走向。写执行语句的时候,脑子里要清楚三件事:数据从哪来、经过什么处理、结果到哪去。想清楚这三点,代码结构就不会乱。

2.2 方案选型:什么时候用什么语句

很多初学者容易犯的一个毛病是“手里有锤子,看什么都像钉子”。学了FOR LOOP就到处用循环,学了游标就什么查询都上游标。实际上不同的执行语句有各自最适合的场景,选错了虽然功能能实现,但性能和可维护性会差很多。

我整理了一个选型对照表,方便你快速判断:

场景推荐方案不推荐方案原因
单行单列查询SELECT INTO显式游标代码简洁,性能好
多行查询逐行处理显式游标 +FOR LOOP隐式游标可控性强,能批量提交
固定次数循环FOR LOOP(数值型)WHILE LOOP不需要手动维护计数器
条件不确定的循环WHILE LOOPFOR LOOP条件驱动更自然
至少执行一次基础LOOPWHILE LOOP先执行后判断
多分支等值判断CASE多层IF可读性更好
范围判断IF-ELSIFCASECASE不适合范围条件
批量 DMLFORALL逐行 DML性能差距巨大

这个表不是死规矩,但大部分情况下按这个来选不会错。核心原则是:能用 SQL 解决的不要用 PL/SQL 循环解决,能用简单语句的不要用复杂语句。PL/SQL 的循环性能远不如原生 SQL,这是很多人踩过的坑。

2.3 执行语句的书写规范

在展开具体语法之前,先说几个书写规范。这些不是语法强制要求,但按这个来写,代码可读性和可维护性会好很多。

缩进:BEGIN ... END内部的语句统一缩进两个或四个空格,嵌套的IF、LOOP再往里缩进。不要用 Tab 和空格混着来,不同编辑器显示效果不一样。

命名:变量用v_前缀,常量用c_前缀,游标用cur_前缀,参数用p_前缀。这样一眼就能看出标识符的类型,不用翻到声明部分去确认。

注释:单行注释用--,多行注释用/* */。每个逻辑块前面加一行注释说明意图,不要注释“这行是赋值”这种废话,要注释“为什么这么赋值”。

大小写:关键字统一大写(BEGIN、END、IF、LOOP),标识符统一小写。这样视觉上容易区分。

这些规范看起来琐碎,但实际项目中,代码是给人看的,机器只是顺便执行一下。规范写出来的代码,三个月后自己回头看还能快速理解。

3. 核心执行语句的细节解析与实操要点

3.1 赋值语句:不只是等号那么简单

赋值语句是执行语句里最基础的一类,但坑也不少。PL/SQL 里的赋值用:=,不是=。=在 PL/SQL 里是比较运算符,用在IF条件里。这个区别初学者经常搞混。

DECLARE v_name VARCHAR2(50); v_count NUMBER := 0; -- 声明时赋初值 BEGIN v_name := '张三'; -- 执行部分赋值 v_count := v_count + 1; END;

除了直接赋值,还有两种特殊的赋值方式需要重点掌握。

SELECT INTO 赋值:从数据库查询结果直接赋给变量。这个用起来方便,但有两个必须处理的异常:NO_DATA_FOUND(查询没结果)和TOO_MANY_ROWS(查询结果超过一行)。

BEGIN SELECT employee_name INTO v_name FROM employees WHERE employee_id = 100; EXCEPTION WHEN NO_DATA_FOUND THEN v_name := '未知'; WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE('查询结果不唯一'); END;

注意:SELECT INTO必须返回且只返回一行。如果业务上可能返回多行,要么加ROWNUM = 1限制,要么改用游标。不加异常处理的SELECT INTO是生产事故的高发区。

游标赋值:通过FETCH把游标当前行的数据赋给变量。这个后面讲游标的时候详细说。

赋值语句还有一个容易忽略的点:空值传播。任何包含NULL的算术运算结果都是NULL。比如v_count := NULL + 1,结果还是NULL,不是 1。所以变量声明时最好给个初值,避免空值参与运算。

3.2 条件控制:IF 和 CASE 怎么选

条件控制语句决定程序的走向。PL/SQL 提供两种:IF和CASE。

IF 语句有三种形式:

-- 形式一:单分支 IF condition THEN statements; END IF; -- 形式二:双分支 IF condition THEN statements; ELSE statements; END IF; -- 形式三:多分支 IF condition1 THEN statements; ELSIF condition2 THEN statements; ELSE statements; END IF;

注意ELSIF的拼写,不是ELSEIF,也不是ELSE IF。这个拼写错误编译器会直接报错,但初学者经常打错。

CASE 语句有两种形式:简单 CASE 和搜索 CASE。

-- 简单 CASE:基于一个表达式的值做等值判断 CASE v_grade WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('优秀'); WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('良好'); WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('及格'); ELSE DBMS_OUTPUT.PUT_LINE('不及格'); END CASE; -- 搜索 CASE:基于多个条件表达式 CASE WHEN v_score >= 90 THEN DBMS_OUTPUT.PUT_LINE('优秀'); WHEN v_score >= 80 THEN DBMS_OUTPUT.PUT_LINE('良好'); WHEN v_score >= 60 THEN DBMS_OUTPUT.PUT_LINE('及格'); ELSE DBMS_OUTPUT.PUT_LINE('不及格'); END CASE;

选型建议:等值判断用简单 CASE,范围判断用搜索 CASE。如果分支超过三个,CASE 通常比嵌套 IF 更清晰。

注意:CASE 语句里如果没有匹配的WHEN且没有ELSE,会抛出CASE_NOT_FOUND异常。所以要么保证覆盖所有情况,要么加ELSE兜底。这一点和 IF 不同,IF 没有匹配就是什么都不做,不会报错。

3.3 循环语句:三种循环的适用场景

PL/SQL 有三种循环:基础LOOP、WHILE LOOP、FOR LOOP。它们的区别在于循环条件的判断时机和循环变量的管理方式。

基础 LOOP:先执行循环体,再判断是否退出。至少执行一次。

LOOP statements; EXIT WHEN condition; END LOOP;

WHILE LOOP:先判断条件,条件为真才执行循环体。可能一次都不执行。

WHILE condition LOOP statements; END LOOP;

FOR LOOP:循环变量自动管理,从下界到上界自动递增或递减。

FOR i IN 1..10 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; -- 递减循环 FOR i IN REVERSE 10..1 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP;

FOR LOOP 的循环变量不需要在声明部分定义,它是循环内部自动声明的。循环结束后这个变量就不存在了,不能在循环外面引用。

循环控制有两个关键字:EXIT和CONTINUE。

  • EXIT:跳出整个循环
  • EXIT WHEN condition:条件满足时跳出
  • CONTINUE:跳过本次循环剩余部分,进入下一次循环
  • CONTINUE WHEN condition:条件满足时跳过
FOR i IN 1..10 LOOP CONTINUE WHEN MOD(i, 2) = 0; -- 跳过偶数 DBMS_OUTPUT.PUT_LINE(i); -- 只输出奇数 END LOOP;

注意:循环里一定要有退出条件,否则就是死循环。基础 LOOP 最容易写出死循环,因为退出条件写在循环体内部,如果条件永远不满足,程序就卡住了。写基础 LOOP 的时候,先写EXIT WHEN,再写业务逻辑,这样不容易忘。

3.4 游标:处理多行查询的正确姿势

游标是 PL/SQL 里处理多行查询结果的核心工具。分显式游标和隐式游标两种。

显式游标的使用分四步:声明、打开、提取、关闭。

DECLARE CURSOR cur_emp IS SELECT employee_id, employee_name FROM employees WHERE department_id = 10; v_id employees.employee_id%TYPE; v_name employees.employee_name%TYPE; BEGIN OPEN cur_emp; LOOP FETCH cur_emp INTO v_id, v_name; EXIT WHEN cur_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_id || ' - ' || v_name); END LOOP; CLOSE cur_emp; END;

游标有四个属性:

属性含义使用时机
%FOUND最近一次 FETCH 是否取到数据FETCH 之后
%NOTFOUND最近一次 FETCH 是否没取到数据FETCH 之后
%ROWCOUNT到目前为止取到的行数FETCH 之后
%ISOPEN游标是否处于打开状态OPEN 之后

游标 FOR 循环是更简洁的写法,不需要手动 OPEN、FETCH、CLOSE,Oracle 自动管理。

BEGIN FOR rec IN (SELECT employee_id, employee_name FROM employees WHERE department_id = 10) LOOP DBMS_OUTPUT.PUT_LINE(rec.employee_id || ' - ' || rec.employee_name); END LOOP; END;

游标 FOR 循环的rec是记录类型变量,自动匹配查询的列结构。循环结束后自动关闭游标,不用手动处理。实际开发中,除非有特殊需求(比如需要中途退出后重新打开),否则优先用游标 FOR 循环。

隐式游标是 Oracle 自动创建的,用于SELECT INTO、INSERT、UPDATE、DELETE等语句。通过SQL%FOUND、SQL%ROWCOUNT等属性访问。

BEGIN UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10; IF SQL%ROWCOUNT > 0 THEN DBMS_OUTPUT.PUT_LINE('更新了 ' || SQL%ROWCOUNT || ' 行'); END IF; END;

注意:隐式游标的属性只在最近一次 SQL 语句执行后有效。如果中间执行了其他 SQL,属性值会被覆盖。所以要在 SQL 执行后立即读取属性值。

3.5 动态 SQL:EXECUTE IMMEDIATE 的使用

有时候 SQL 语句在编译时不确定,需要运行时动态拼接,这时候用EXECUTE IMMEDIATE。

DECLARE v_table_name VARCHAR2(30) := 'employees'; v_sql VARCHAR2(500); v_count NUMBER; BEGIN v_sql := 'SELECT COUNT(*) FROM ' || v_table_name || ' WHERE department_id = :1'; EXECUTE IMMEDIATE v_sql INTO v_count USING 10; DBMS_OUTPUT.PUT_LINE('记录数:' || v_count); END;

动态 SQL 的要点:

  • INTO子句接收查询结果,只适用于返回单行的查询
  • USING子句传入绑定变量,避免 SQL 注入
  • 动态 SQL 在运行时才解析,编译时不会检查语法错误
  • 性能比静态 SQL 差,因为每次执行都要解析

注意:动态 SQL 里拼接表名、列名的时候要特别小心,这些不能作为绑定变量传入,只能字符串拼接。如果来源不可信,必须做白名单校验,否则有 SQL 注入风险。绑定变量只能用于值,不能用于标识符。

4. 完整实操案例:从需求到实现

4.1 需求描述

假设有一个员工表employees,包含employee_id、employee_name、department_id、salary、hire_date几个字段。现在需要写一个 PL/SQL 块,实现以下功能:

  1. 遍历部门 10 的所有员工
  2. 根据入职年限调整薪资:满 5 年涨 10%,满 3 年涨 5%,其他涨 2%
  3. 如果调整后薪资超过 20000,只涨到 20000 为止
  4. 把调整结果写入日志表salary_log
  5. 处理可能出现的异常

4.2 实现思路拆解

这个需求涉及几个关键点:

  • 多行查询:用游标 FOR 循环遍历
  • 条件判断:用搜索 CASE 或 IF-ELSIF
  • 上限控制:用LEAST函数或 IF 判断
  • 数据写入:用INSERT
  • 异常处理:捕获OTHERS并记录

先想清楚数据流:从employees读数据,计算新薪资,写入salary_log。读和写是分开的,中间用变量传递。

4.3 完整代码实现

DECLARE v_new_salary NUMBER; v_raise_rate NUMBER; v_years NUMBER; BEGIN FOR rec IN (SELECT employee_id, employee_name, salary, hire_date FROM employees WHERE department_id = 10) LOOP -- 计算入职年限 v_years := TRUNC(MONTHS_BETWEEN(SYSDATE, rec.hire_date) / 12); -- 根据年限确定涨幅 CASE WHEN v_years >= 5 THEN v_raise_rate := 0.10; WHEN v_years >= 3 THEN v_raise_rate := 0.05; ELSE v_raise_rate := 0.02; END CASE; -- 计算新薪资,上限 20000 v_new_salary := LEAST(rec.salary * (1 + v_raise_rate), 20000); -- 写入日志表 INSERT INTO salary_log (employee_id, old_salary, new_salary, log_date) VALUES (rec.employee_id, rec.salary, v_new_salary, SYSDATE); DBMS_OUTPUT.PUT_LINE(rec.employee_name || ':' || rec.salary || ' -> ' || v_new_salary); END LOOP; COMMIT; DBMS_OUTPUT.PUT_LINE('薪资调整完成'); EXCEPTION WHEN OTHERS THEN ROLLBACK; DBMS_OUTPUT.PUT_LINE('发生错误:' || SQLERRM); RAISE; END;

4.4 关键步骤说明

游标 FOR 循环的选择:这里用游标 FOR 循环而不是显式游标,因为不需要中途退出或重新打开,自动管理更省心。rec自动匹配查询列,直接用rec.employee_id访问。

年限计算:MONTHS_BETWEEN(SYSDATE, hire_date)返回两个日期之间的月数,除以 12 再TRUNC取整得到年数。注意MONTHS_BETWEEN返回的是带小数的数值,直接比较会有精度问题,所以先取整。

涨幅确定:用搜索 CASE 而不是 IF-ELSIF,因为条件是范围判断,CASE 写起来更整齐。注意WHEN的顺序,从大到小排,这样第一个匹配的就是最终结果。

上限控制:用LEAST函数取较小值,比写 IF 判断简洁。LEAST(a, b)返回 a 和 b 中较小的那个。

异常处理:捕获OTHERS后先ROLLBACK再输出错误信息,最后RAISE重新抛出异常。这样既保证了数据一致性,又不会吞掉错误。实际项目中,异常信息应该写入日志表而不是只输出到控制台。

提交时机:循环结束后统一COMMIT,而不是每行提交。批量提交性能更好,但要注意如果循环中途出错,前面的操作会回滚。如果数据量大,可以每 1000 行提交一次,平衡性能和回滚代价。

4.5 参数计算过程

以一个月薪 8000、入职 4 年的员工为例:

  1. 年限计算:MONTHS_BETWEEN(SYSDATE, hire_date)假设返回 48.5,除以 12 得 4.04,TRUNC后为 4
  2. 涨幅确定:4 年满足>= 3但不满足>= 5,所以涨幅 5%
  3. 新薪资计算:8000 * 1.05 = 8400,LEAST(8400, 20000) = 8400
  4. 结果:薪资从 8000 调整为 8400

再以一个月薪 19000、入职 6 年的员工为例:

  1. 年限计算:6 年
  2. 涨幅确定:满足>= 5,涨幅 10%
  3. 新薪资计算:19000 * 1.10 = 20900,LEAST(20900, 20000) = 20000
  4. 结果:薪资从 19000 调整为 20000,被上限截断

这个计算逻辑清晰,每一步都可验证。实际写代码的时候,建议先用几个典型值手动算一遍,确认逻辑正确再写进程序。

5. 常见问题与排查技巧实录

5.1 执行语句常见报错速查

报错信息原因解决方法
ORA-06550: line X, column Y语法错误检查对应行列的语法,常见是漏分号、拼写错误
ORA-01403: no data foundSELECT INTO没查到数据加NO_DATA_FOUND异常处理
ORA-01422: exact fetch returns more than requested number of rowsSELECT INTO返回多行加ROWNUM = 1或改用游标
ORA-06502: numeric or value error变量长度不够或类型不匹配检查变量声明长度和赋值内容
ORA-06531: Reference to uninitialized collection集合未初始化使用前先调用构造函数初始化
ORA-01000: maximum open cursors exceeded游标打开太多没关闭检查游标是否都有关闭,或增大OPEN_CURSORS参数
ORA-06508: PL/SQL: could not find program unit being called调用的存储过程不存在或无效检查对象是否存在,重新编译
ORA-04063: package body has errors包体有编译错误查看编译错误详情,修复后重新编译

5.2 排查思路:从报错到定位

遇到报错不要慌,按这个顺序排查:

第一步:看报错信息。Oracle 的报错信息通常包含错误码、错误描述、出错的行号。行号是最关键的线索,直接定位到具体哪一行。

第二步:检查该行的语法。常见问题:漏分号、括号不匹配、关键字拼写错误、变量名写错。这些低级错误占了报错的一半以上。

第三步:检查变量和类型。如果语法没问题,看涉及的变量是否声明、类型是否匹配、长度是否够。VARCHAR2(10)的变量赋一个 20 字符的字符串就会报ORA-06502。

第四步:检查数据。如果语法和变量都没问题,看查询的数据是否符合预期。SELECT INTO返回多行或零行都会报错,但语法本身没问题。

第五步:加调试输出。在关键位置加DBMS_OUTPUT.PUT_LINE,输出变量值和执行进度,看程序走到哪一步出错。这是最笨但最有效的方法。

5.3 独家避坑技巧

技巧一:先写异常处理框架。写任何 PL/SQL 块,先把EXCEPTION WHEN OTHERS THEN框架搭好,再填业务逻辑。这样即使出错也能看到错误信息,不会一头雾水。

技巧二:循环里慎用 COMMIT。循环里每行提交会导致频繁的 I/O,性能差。而且如果循环中途出错,前面提交的数据无法回滚,数据一致性难保证。建议循环外统一提交,或者每 N 行提交一次。

技巧三:游标用完必须关。显式游标不关会占用数据库资源,长时间运行的程序可能报ORA-01000。用游标 FOR 循环可以避免这个问题,因为自动关闭。

技巧四:动态 SQL 用绑定变量。拼接 SQL 的时候,值用USING传,不要直接拼字符串。直接拼字符串不仅有 SQL 注入风险,还会导致每次执行都重新解析,性能差。

技巧五:注意 NULL 的比较。NULL = NULL的结果是NULL,不是TRUE。判断 NULL 要用IS NULL或IS NOT NULL。条件里涉及可能为 NULL 的变量时要特别小心。

技巧六:FOR LOOP 的边界。FOR i IN 1..10包含 1 和 10,共 10 次。FOR i IN REVERSE 10..1从 10 递减到 1。如果下界大于上界且不用 REVERSE,循环体一次都不执行,不会报错。这个特性有时候会导致逻辑静默失败。

技巧七:CASE 的 ELSE 不是可有可无。CASE 语句没有匹配且没有 ELSE 会抛CASE_NOT_FOUND异常。即使你认为覆盖了所有情况,加个 ELSE 兜底也更安全。

技巧八:变量命名避免关键字。比如date、number、value这些词虽然不全是保留字,但用作变量名容易引起混淆。加前缀v_可以有效避免。

5.4 性能优化要点

执行语句写对了只是第一步,写得快才是进阶要求。几个关键优化点:

减少循环内的 SQL。循环里每执行一次 SQL 就是一次网络往返和解析。如果循环 1000 次,就是 1000 次 SQL 执行。能改成批量操作的尽量批量。比如循环里INSERT,可以改成FORALL批量插入。

用 BULK COLLECT 批量提取。游标逐行 FETCH 性能差,用BULK COLLECT INTO一次性提取到集合里,再在内存中处理。

DECLARE TYPE t_emp IS TABLE OF employees%ROWTYPE; v_emps t_emp; BEGIN SELECT * BULK COLLECT INTO v_emps FROM employees WHERE department_id = 10; FOR i IN 1..v_emps.COUNT LOOP -- 处理 v_emps(i) END LOOP; END;

避免在循环条件里做查询。WHILE (SELECT COUNT(*) FROM ...) > 0 LOOP这种写法每次循环都查一次数据库,性能极差。应该先查出来存到变量里,循环里用变量判断。

用 EXISTS 代替 COUNT(*)。判断记录是否存在,EXISTS比COUNT(*)快,因为找到一条就返回,不用统计全部。

这些优化技巧在实际项目中能带来数量级的性能提升,尤其是数据量大的时候。我见过一个程序,优化前跑 20 分钟,优化后 30 秒,差别就在这些细节上。

6. 执行语句的进阶用法与扩展思路

6.1 嵌套块与作用域

PL/SQL 允许在BEGIN ... END里再嵌套BEGIN ... END,形成嵌套块。嵌套块里可以声明自己的变量,作用域仅限于该块内部。

DECLARE v_outer VARCHAR2(10) := 'outer'; BEGIN DECLARE v_inner VARCHAR2(10) := 'inner'; BEGIN DBMS_OUTPUT.PUT_LINE(v_outer || ' ' || v_inner); -- 都能访问 END; -- DBMS_OUTPUT.PUT_LINE(v_inner); -- 报错,v_inner 超出作用域 END;

嵌套块的主要用途是隔离异常处理。内层块出错,如果不处理会传播到外层块。如果内层块自己处理了,外层块感知不到。

BEGIN BEGIN -- 可能出错的代码 NULL; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('内层处理了错误'); END; DBMS_OUTPUT.PUT_LINE('外层继续执行'); END;

这个特性在批量处理的时候很有用:单条记录出错不影响其他记录继续处理。

6.2 GOTO 和 NULL 语句

GOTO语句无条件跳转到指定标签。实际开发中强烈不建议使用,因为它破坏程序的结构化流程,让代码难以理解和维护。几乎所有GOTO能实现的功能,都能用条件判断和循环更好地实现。

BEGIN GOTO label1; DBMS_OUTPUT.PUT_LINE('这行不会执行'); <<label1>> DBMS_OUTPUT.PUT_LINE('跳转到这里'); END;

NULL语句是一个空操作,什么都不做。主要用途是占位,让语法完整。比如IF分支里暂时不想写逻辑,但不能留空,就写个NULL。

IF condition THEN NULL; -- 暂时不处理 ELSE DBMS_OUTPUT.PUT_LINE('处理其他情况'); END IF;

6.3 执行语句的调试方法

PL/SQL 没有断点调试那么方便的工具,主要靠输出调试。DBMS_OUTPUT.PUT_LINE是最常用的手段,但要注意几点:

  • 需要先执行SET SERVEROUTPUT ON才能看到输出
  • 输出缓冲区有大小限制,输出太多会报ORA-20000
  • 生产环境不要留调试输出,影响性能

更专业的做法是建一个日志表,把调试信息写进去,带时间戳和模块名,方便事后分析。

CREATE TABLE debug_log ( log_id NUMBER GENERATED ALWAYS AS IDENTITY, log_time TIMESTAMP DEFAULT SYSTIMESTAMP, module VARCHAR2(100), message VARCHAR2(4000) ); -- 写日志 INSERT INTO debug_log (module, message) VALUES ('PROC_001', '开始处理');

这种方式比DBMS_OUTPUT灵活,可以随时查询,不受会话限制。

6.4 事务控制与执行语句的配合

执行语句里的 DML 操作涉及事务控制。COMMIT提交事务,ROLLBACK回滚事务,SAVEPOINT设置保存点。

BEGIN INSERT INTO t1 VALUES (1); SAVEPOINT sp1; INSERT INTO t2 VALUES (2); ROLLBACK TO sp1; -- 回滚到 sp1,t2 的插入被撤销,t1 的插入保留 COMMIT; END;

事务控制的关键原则:

  • 事务要尽量短,长时间不提交会占用锁资源
  • 循环里批量 DML 时,定期提交,避免回滚段过大
  • 异常处理里记得ROLLBACK,保证数据一致性
  • DDL 语句(CREATE、ALTER、DROP)会自动提交事务,不能回滚

注意:COMMIT之后不能再ROLLBACK。如果程序逻辑需要回滚,确保在COMMIT之前完成所有判断。这个顺序搞反了会导致数据无法回滚。

7. 写在最后的一些实操体会

PL/SQL 的执行语句看起来简单,真写好需要大量练习。我的建议是:不要光看语法,一定要动手写。找一个实际的业务场景,从简单的开始,逐步增加复杂度。写完之后自己 review 一遍,看看有没有漏掉的异常处理、有没有可以优化的地方。

另外,养成看执行计划的习惯。很多性能问题在写代码的时候就能避免,关键是脑子里要有“这条语句会怎么执行”的概念。比如循环里查数据库,你知道每次循环都是一次网络往返,自然就会想办法改成批量操作。

最后分享一个我自己的习惯:写 PL/SQL 的时候,先把异常处理框架和日志输出写好,再填业务逻辑。这样即使逻辑写错了,也能通过日志快速定位问题。这个习惯帮我省了很多排查时间,尤其是在处理复杂业务逻辑的时候。

执行语句这块内容比较多,一篇写不完。后面有机会再聊聊存储过程、函数、触发器的执行语句有什么不同,以及包(PACKAGE)里的执行语句怎么组织。先把这篇的内容消化掉,动手写几个例子,比看十篇文章都管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 16:43:14

前馈式3D高斯泼溅:快20倍的三维感知新范式深度解析

前馈式3D高斯泼溅&#xff08;Feed-forward 3D Gaussian Splatting&#xff09;最近在三维视觉圈里的声量一下子上来了。起因是某高校和某科技公司联合放出了一项研究成果&#xff1a;一套前馈式GS算法&#xff0c;在从环视相机图像构建三维场景结构这件事上&#xff0c;比某头…

作者头像 李华
网站建设 2026/10/22 16:43:07

CVE-2025-27591深度解析:日志工具Below的符号链接提权漏洞与修复策略

CVE-2025-27591这个编号最近在安全圈子里讨论度不低&#xff0c;因为它落在一款不少人都天天在用的终端日志工具身上——Below。这年头一说到日志查看器&#xff0c;大家第一反应多半是“这东西能有什么风险”&#xff0c;但这条漏洞恰恰提醒了一件事&#xff1a;你随手敲的脚本…

作者头像 李华
网站建设 2026/10/22 16:41:23

单北斗设备变形监测选型与系统落地全解析

2026年的监测设备选型市场&#xff0c;几乎已经绕不开一个词&#xff1a;单北斗。最近在帮几个项目做设备评估&#xff0c;发现甲方的需求描述已经从"支持北斗"变成了"必须单北斗",而且不是菜单里钩掉GPS那么简单。所谓单北斗&#xff0c;指的是接收机硬件…

作者头像 李华
网站建设 2026/10/22 16:37:32

从零构建可插拔的Skills能力封装与调度系统

1. 当“skills”成为一个项目标题&#xff1a;我看到的不是技能清单&#xff0c;而是一套能力封装系统第一次看到“skills”这个标题时&#xff0c;我下意识以为又是一个罗列“程序员必会的十项技能”之类的清单文。但翻完项目正文、关键词和摘要描述之后&#xff0c;我发现输入…

作者头像 李华
网站建设 2026/10/22 16:37:31

基于逻辑回归的股票客户流失预警模型:从特征工程到业务落地

做股票类App运营和数据的人&#xff0c;基本都会遇到同一个头疼点&#xff1a;客户明明没有销户&#xff0c;却连续几个月不登录、不交易&#xff0c;等行情一来&#xff0c;人已经悄悄跑到别家平台去了。我在参与这类项目时最常用的方案&#xff0c;就是基于交易行为数据训练一…

作者头像 李华
网站建设 2026/10/22 16:37:31

HBase 2.3.7集群部署全指南:从环境检查到生产配置避坑

1. 为什么还在装 HBase 2.3.7 HBase 2.3.7 这个版本在不少团队里服役了好几年&#xff0c;用起来有个特点&#xff1a;稳。新版本功能确实多&#xff0c;但生产环境图的是省心&#xff0c;2.3.7 处于 2.x 分支相对成熟的维护期&#xff0c;很多底层 bug 都已经修掉&#xff0c;…

作者头像 李华