NOCOPY是Oracle对IN OUT/OUT参数的引用传递优化提示,可避免大对象复制开销,但会改变异常语义——异常时已赋值的参数仍保留修改,可能导致数据不一致。

IN OUT参数默认是值传递,复制开销随数据量陡增
Oracle对IN OUT和OUT参数默认采用值传递(pass-by-value):调用时把整个参数内容复制一份进过程栈;返回时再把修改后的副本复制回实参。当参数是大型集合(如DBMS_SQL.VARCHAR2A)、嵌套表或长CLOB时,两次完整内存拷贝会显著拖慢执行速度。
NOCOPY让OUT/IN OUT参数尽量走引用传递
NOCOPY不是强制指令,而是向编译器发出的优化提示——“如果安全,就别复制,直接操作原变量内存地址”。它生效的前提包括:
- 参数类型支持引用传递(如PL/SQL集合、记录、对象类型)
- 过程未被标记为
AUTONOMOUS_TRANSACTION - 调用栈中没有异常传播路径干扰(比如未在异常块里修改该参数)
一旦生效,OUT参数赋值后立即反映到调用方变量,省去返回时的复制步骤;IN OUT参数则全程只有一份内存实体。
但NOCOPY会改变异常语义,这点极易被忽略
默认OUT参数在异常发生时保持原值不变;而加了NOCOPY后,只要赋过值,即使过程中途报错,实参也会保留已写入的值。例如:
DECLARE
v_data SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST('a','b');
PROCEDURE p_test (p_out OUT NOCOPY SYS.ODCIVARCHAR2LIST) IS
BEGIN
p_out := SYS.ODCIVARCHAR2LIST('x','y','z');
RAISE_APPLICATION_ERROR(-20001, 'boom');
END;
BEGIN
p_test(v_data);
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(v_data.COUNT); -- 输出 3,不是原来的 2
END;这种行为在批量处理中可能引发数据不一致——你以为失败了,其实部分输出已落库。
何时该用NOCOPY,何时该放弃
适合用NOCOPY的场景:
- 参数是大容量集合(元素数 > 1000),且过程逻辑稳定、异常概率低
- 过程纯计算无副作用,或你明确需要“异常中也保留中间状态”
应避免NOCOPY的场景:
- 参数是
REF CURSOR或LOBlocator(Oracle不保证NOCOPY对其生效) - 过程包含复杂事务控制或依赖
OUT参数的原子性语义 - 调用方代码假设“异常=参数未变更”,且无法修改该假设
真正影响性能的从来不是NOCOPY本身,而是你是否清楚它绕过了Oracle默认的值保护机制。


















