no_data_found仅在select into或含该语句的存储过程中触发,update/delete不抛此异常;需用嵌套块捕获以避免中断执行,聚合函数可规避但有性能与逻辑风险。

NO_DATA_FOUND只在SELECT INTO中触发,别在UPDATE里捕获它
NO_DATA_FOUND不是万能异常钩子,它只对隐式单行查询生效。执行UPDATE、DELETE或BULK COLLECT时即使没匹配到数据,也绝不会抛出这个异常——SQL%ROWCOUNT会是0,但流程照常往下走。常见错误是把WHEN NO_DATA_FOUND THEN写在DML语句后面,结果根本进不去这个分支。
真正会触发它的场景只有两个:SELECT ... INTO语句,以及PL/SQL块中调用的、内部含SELECT INTO的存储过程(且未被其自身异常块拦截)。
EXCEPTION块里别只写DBMS_OUTPUT.PUT_LINE
日志输出只是调试手段,上线后不记录、不返回、不重试,等于没处理。实际业务中必须明确“无数据”意味着什么:
- 是正常边界情况?比如查用户偏好,没设就用默认值 → 给变量赋
v_pref := 'DEFAULT' - 是需降级逻辑?比如主表查不到就查历史快照表 → 在
WHEN NO_DATA_FOUND THEN里再执行一次SELECT ... INTO查备份视图 - 是严重数据不一致?比如根据订单ID反查客户不存在 → 用
RAISE_APPLICATION_ERROR(-20001, '客户主键丢失,订单ID: ' || v_order_id)中止并暴露问题
漏掉这层判断,异常块就只剩个空壳。
嵌套BEGIN-END才能让异常后继续执行
很多人写完EXCEPTION块发现后续语句不跑了,是因为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;</p>
不加这层嵌套,DBMS_OUTPUT那行就永远执行不了。
聚合函数MAX/MIN能绕过异常,但有副作用
把SELECT col INTO v_col改成SELECT MAX(col) INTO v_col确实不会报NO_DATA_FOUND,因为聚合函数空集返回NULL而非异常。但要注意:
-
MAX()和MIN()会强制全表扫描(哪怕加了索引),而原SELECT ... WHERE可能走索引快速定位 - 如果业务要求“必须是唯一匹配”,用
MAX()会掩盖TOO_MANY_ROWS错误——多个值时它只取一个,静默丢数据 -
NVL(col, 'default')没用,它处理的是NULL,不是“无行”,照样抛异常
用聚合函数是权宜之计,不是替代异常处理的设计方案。
真实项目里最常踩的坑,是把NO_DATA_FOUND当成通用“查不到”的开关,却忽略了它严格绑定SELECT INTO语义,以及异常传播会中断执行流这两个硬约束。











