execute immediate 足以覆盖95%动态sql场景,但需严防sql注入(结构参数须白名单或dbms_assert校验)、ddl自动提交不可回滚、多行查询宜用open-for游标、错误处理须保留sqlerrm/sqlcode及脱敏sql日志。
直接用 execute immediate 就够了,95% 的动态 sql 场景不需要碰 dbms_sql。 它简洁、高效、原生支持 ddl/dml/select,但用错就容易出安全漏洞或运行时错误。
为什么不能拼接用户输入的表名或列名
绑定变量(USING)只保护「值」,不保护「结构」。比如 WHERE name = :val 中的 :val 可以安全传入任意字符串;但 'SELECT * FROM ' || user_table 里的 user_table 是直接拼进语句的——如果传入 'employees; DROP TABLE users; --',就会执行恶意 DDL。
必须做白名单校验或调用 DBMS_ASSERT:
-
DBMS_ASSERT.SQL_OBJECT_NAME('employees')→ 合法,返回'employees' -
DBMS_ASSERT.SQL_OBJECT_NAME('employees; DROP TABLE users')→ 抛异常ORA-44003: invalid SQL name - 对字段名、ORDER BY 列等结构类参数,也得走同样校验流程
DDL 语句必须用 EXECUTE IMMEDIATE,且不能带 INTO 或 USING
Oracle PL/SQL 块里无法直接写 CREATE TABLE 或 TRUNCATE TABLE,编译就报错。但 EXECUTE IMMEDIATE 是唯一能绕过这个限制的合法方式。
注意三点:
- DDL 自动提交,执行后无法回滚 —— 如果在事务中调用,会隐式结束当前事务
- 不能加
INTO(没返回结果集)、不能加USING(DDL 不接受绑定值) - 若需动态建索引或授予权限,语句必须完整合法,例如:
EXECUTE IMMEDIATE 'CREATE INDEX idx_emp_dept ON employees(dept_id)'
查询多行数据时,别硬扛 EXECUTE IMMEDIATE
EXECUTE IMMEDIATE 只支持单行 SELECT ... INTO,或多行用 BULK COLLECT INTO。但如果你要逐行处理、分页、或结果集结构不确定,硬写 BULK COLLECT 容易内存溢出或逻辑断裂。
更稳妥的做法是换用 OPEN-FOR 动态游标:
DECLARE
v_cursor SYS_REFCURSOR;
v_sql VARCHAR2(500);
v_name employees.last_name%TYPE;
BEGIN
v_sql := 'SELECT last_name FROM employees WHERE department_id = :dept';
OPEN v_cursor FOR v_sql USING 10;
LOOP
FETCH v_cursor INTO v_name;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_name);
END LOOP;
CLOSE v_cursor;
END;
它复用静态游标习惯,支持任意行数,且不把全部结果加载进 PGA。
错误处理不能只写 WHEN OTHERS THEN NULL
动态 SQL 出错时,真实错误信息(如 ORA-00942: table or view does not exist)会被吞掉,只剩空异常,排查成本陡增。
至少保留原始错误上下文:
- 记录
SQLERRM和SQLCODE - 把构造的
v_sql一并打日志(注意脱敏敏感值) - DDL 失败时,尤其要检查权限和对象是否存在 —— 这类问题不会出现在开发环境,只在部署后爆发
最常被忽略的是:DDL 执行失败后,后续语句仍可能继续运行,导致状态不一致。务必在关键路径上加显式 RAISE 或事务控制。











