直接用 execute immediate 就能安全执行绝大多数动态 sql,前提是值走 using、结构(表名/列名)走 dbms_assert 校验,且不把多行查询硬塞进 into。

直接用 EXECUTE IMMEDIATE 就能安全执行绝大多数动态 SQL,前提是值走 USING、结构(表名/列名)走 DBMS_ASSERT 校验,且不把多行查询硬塞进 INTO。
绑定变量必须用 USING,不能拼字符串
所有用户可控的「值」——比如 WHERE 条件、UPDATE 的 SET 值、INSERT 的字段值——必须用占位符 + USING 传入。拼接字符串等于主动交出数据库控制权。
- ❌ 错误:
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = ''' || user_name || ''''—— 单引号闭合后可注入OR 1=1 -- - ✅ 正确:
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = :n' USING user_name—— Oracle 在解析阶段就锁定语句结构,运行时只代入值 - ⚠️ 注意:
USING后变量顺序必须和 SQL 中占位符出现顺序严格一致;:n是命名占位符,但 Oracle 不按名字匹配,而是按位置 - ⚠️
USING只接受 PL/SQL 变量,不能是表达式,比如USING UPPER(name)会报错,得先v_name := UPPER(name);再USING v_name
表名、列名、ORDER BY 字段必须校验,不能直接拼
USING 只保护「值」,不保护「结构」。表名、字段名、排序字段这些属于 SQL 语法骨架,拼错一个字符或混入恶意语句就会执行 DDL 或绕过权限。
- ❌ 危险:
EXECUTE IMMEDIATE 'SELECT * FROM ' || user_table || ' WHERE id = :1' USING id_val——user_table若为employees; DROP TABLE users; --,就真删表了 - ✅ 安全:
EXECUTE IMMEDIATE 'SELECT * FROM ' || DBMS_ASSERT.SQL_OBJECT_NAME(user_table) || ' WHERE id = :1' USING id_val—— 非法名直接抛ORA-44003,不执行 - ✅ 替代方案:白名单校验,比如
CASE user_table WHEN 'employees' THEN 'employees' WHEN 'departments' THEN 'departments' ELSE RAISE_APPLICATION_ERROR(-20001, 'invalid table') - ⚠️ 别信正则过滤:
REGEXP_LIKE(user_table, '^[a-zA-Z][a-zA-Z0-9_]*$')挡不住 Unicode 变体或宽字节绕过
DDL 必须用 EXECUTE IMMEDIATE,但自动提交不可回滚
PL/SQL 块里写 CREATE TABLE 或 TRUNCATE TABLE 会编译失败(PLS-00103),唯一合法方式就是 EXECUTE IMMEDIATE 执行字符串。但它有强副作用,容易踩坑。
- ✅ 必须:
EXECUTE IMMEDIATE 'CREATE INDEX idx_emp_dept ON employees(dept_id)'—— DDL 只能这么干 - ❌ 不能加
INTO或USING:EXECUTE IMMEDIATE 'DROP TABLE ' || tab_name USING tab_name会报ORA-00900 - ⚠️ DDL 自动提交:哪怕在事务中间执行,也会隐式
COMMIT,前面的 DML 就无法回滚 - ⚠️ 如果需要事务一致性(比如建表失败则整个事务回滚),只能换用
DBMS_SQL包,但性能差、代码冗长,95% 场景没必要
多行查询别硬套 INTO,优先用 OPEN-FOR 游标
EXECUTE IMMEDIATE ... INTO 只支持单行结果;BULK COLLECT INTO 能取多行,但会一次性加载全部数据进 PGA,容易内存溢出或阻塞。
- ❌ 危险:
EXECUTE IMMEDIATE 'SELECT last_name FROM employees' BULK COLLECT INTO name_list—— 表大一点就 OOM - ✅ 推荐:
OPEN v_cursor FOR v_sql USING dept_id+ 循环FETCH—— 每次只取一批,可控内存,支持分页和中途退出 - ✅ 示例中
v_sql仍需对结构参数(如last_name字段名)做DBMS_ASSERT校验,值参数(dept_id)走USING - ⚠️
EXECUTE IMMEDIATE无法返回游标变量,想把动态查询结果交给调用方,必须用OPEN-FOR并返回SYS_REFCURSOR
最易被忽略的是:结构参数校验和值参数绑定必须同时存在,缺一不可。只做 USING 不防表名注入,只做 DBMS_ASSERT 不防 WHERE 条件注入——两者是并行防线,不是二选一。











