NO_DATA_FOUND仅在SELECT INTO未查到数据时触发,DML语句不抛此异常;必须用嵌套块捕获以保障后续逻辑执行,且需按业务含义做差异化处理。

NO_DATA_FOUND只在SELECT INTO里生效,别在UPDATE/DELETE后捕获它
这个异常根本不会在DML语句后抛出——UPDATE或DELETE没匹配到行时,SQL%ROWCOUNT返回0,但流程继续执行。常见错误是把WHEN NO_DATA_FOUND THEN写在UPDATE后面,结果永远进不去该分支。
真正触发它的场景只有两个:
-
SELECT ... INTO语句查不到数据 - 调用的子过程内部含
SELECT ... INTO,且该子过程没自己处理这个异常
误以为它是“通用空结果钩子”,会导致异常块形同虚设。
嵌套BEGIN-END是让后续逻辑继续执行的关键
PL/SQL中一旦异常抛出,当前BEGIN-END块立即终止。想捕获后还能跑后续代码(比如日志、清理、返回默认值),必须把SELECT INTO包进独立的嵌套块:
DECLARE
v_name VARCHAR2(50);
BEGIN
BEGIN
SELECT ename INTO v_name FROM emp WHERE empno = 9999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_name := 'UNKNOWN';
END;
<p>DBMS_OUTPUT.PUT_LINE('员工名:' || v_name); -- 这行能执行到
-- 后续其他操作也在这里继续
END;不加这层嵌套,DBMS_OUTPUT那行就直接跳过了。
别用SELECT COUNT(*)预检,聚合函数有副作用
先SELECT COUNT(*) INTO v_cnt再判断是否>0,接着再SELECT ... INTO——这种写法性能差、有竞态风险:
- 两次查询,索引扫描或全表扫描开销翻倍
- 两次查询之间数据可能被删,导致“查到count=1,但第二次SELECT INTO报NO_DATA_FOUND”
-
MAX()/MIN()虽不抛异常,但会强制走全表扫描(哪怕字段有索引),且掩盖TOO_MANY_ROWS问题
真正需要“唯一匹配”的场景,用SELECT ... INTO + EXCEPTION才是语义清晰、性能可控的做法。
处理逻辑必须区分业务含义,不能只打日志
DBMS_OUTPUT.PUT_LINE只是调试手段,上线后等于没处理。关键要看“查不到”对当前业务意味着什么:
- 是正常边界?比如查用户偏好,没设就赋默认值:
v_pref := 'DEFAULT' - 需降级?比如主表查不到就查历史快照:
SELECT ... INTO ... FROM emp_history WHERE ... - 属严重数据异常?比如订单ID反查客户不存在,应中止并暴露:
RAISE_APPLICATION_ERROR(-20001, '客户主键丢失,订单ID: ' || v_order_id)
漏掉这层判断,EXCEPTION块就只剩个空壳;更危险的是,事务状态可能已改变(如前面的UPDATE已执行),却没做ROLLBACK或记录日志。

















