\_nocopy仅适用于in out/out大型集合参数且子程序只读或局部修改时,可减少拷贝开销;对标量无效,异常时可能导致实参部分修改,且受表达式传参、复杂类型、调试模式等限制常被忽略。
什么时候该用 _nocopy?别一上来就加
只有当传入的是大型集合(比如 varchar2(32767) 数组、record 或嵌套表)且过程内部只读或仅做局部修改时,_nocopy 才可能带来收益。普通标量参数(number、date)加了没用,反而埋雷。
- Oracle 默认按值传递(copy-in/copy-out),函数退出时才把修改写回实参;
_NOCOPY改为传引用,实参内存地址直接透传进子程序 - 如果子程序中途异常退出,
_NOCOPY下实参可能已部分被改写,而默认行为下实参完全不受影响 - 在
FORALL+BULK COLLECT场景中,大集合反复进出子程序时,去掉拷贝能省几毫秒——但得先确认这是瓶颈,别过早优化
_NOCOPY 为什么有时不生效?编译器会悄悄忽略
Oracle 不保证一定按引用传递,即使写了 _NOCOPY,也可能因以下原因退回到拷贝模式:
- 参数是
IN OUT或OUT,但调用时传的是表达式(如my_proc(x + 1))、字面量(my_proc('abc'))或常量(my_proc(c_my_const))——这些没法取地址,强制拷贝 - 参数类型含
%ROWTYPE或%TYPE且底层对象结构复杂(比如字段含LOB),Oracle 认为安全起见仍走拷贝 - 开启了调试模式(
ALTER SESSION SET PLSQL_DEBUG = TRUE),所有_NOCOPY自动失效
验证是否真生效?用 DBMS_OUTPUT.PUT_LINE 打印参数地址(UTL_RAW.CAST_FROM_NUMBER 配合 DBMS_UTILITY.GET_HASH_VALUE 不靠谱),更稳的方式是测执行时间差异 + V$SQL_PLAN 看是否减少内存分配。
怎么写才不会踩坑?三条硬约束
_NOCOPY 是提示(hint),不是指令,必须配合明确的使用契约,否则容易引发静默错误:
- 只对
IN OUT和OUT参数加,IN参数加了无效(Oracle 忽略) - 调用方必须传变量名,不能传表达式;检查调用处是不是写了类似
proc(my_array)而不是proc(my_array || 'x') - 子程序里别假设参数“干净”——万一上游已经用
_NOCOPY传进来,你再把它当全新容器清空(my_tab.DELETE),可能误删调用方数据
示例对比:
PROCEDURE bad_example(p_list IN OUT NOCOPY t_string_list) IS BEGIN p_list.DELETE; -- 危险!调用方数组可能被清空 p_list.EXTEND(1); p_list(1) := 'new'; END; <p>PROCEDURE good_example(p_list IN OUT NOCOPY t_string_list) IS l_local_copy t_string_list := p_list; -- 显式复制,隔离副作用 BEGIN l_local_copy.DELETE; l_local_copy.EXTEND(1); l_local_copy(1) := 'new'; p_list := l_local_copy; -- 最后一次性赋值 END;</p>
替代方案比 _NOCOPY 更常用:用包变量或返回集合
真正要避免大集合拷贝,多数时候不如换思路:
- 把大集合声明为包级变量(
PACKAGE BODY里的g_large_table),子程序直接操作,彻底绕过参数传递 - 把过程改成函数,返回新集合(
FUNCTION get_filtered(p_src t_tab) RETURN t_tab),语义清晰,调用方可控生命周期 - 用临时表(
GLOBAL TEMPORARY TABLE)存中间结果,适合跨多层调用、需事务一致性的场景
_NOCOPY 的适用面其实很窄——它解决的是“必须用参数传大集合 + 必须频繁调用 + 已确认拷贝是瓶颈”这三重交集。大多数业务逻辑里,可读性、健壮性比那点内存节省重要得多。










