动态SQL中必须用绑定变量而非字符串拼接来防止SQL注入,所有外部输入需校验;EXECUTE IMMEDIATE要求绑定变量位置、数量、类型严格匹配;DBMS_SQL更灵活但易出错,95%场景用EXECUTE IMMEDIATE即可;权限需按调用者或定义者明确配置,避免隐式越权。
动态SQL里用 EXECUTE IMMEDIATE 时,为什么不能拼字符串
因为拼接用户输入的字符串(比如 'select * from users where name = ''' || user_input || '''')会让恶意输入直接进入执行计划,数据库不区分“代码”和“数据”。一旦 user_input 是 ' or 1=1 --,整条语句就变成 select * from users where name = '' or 1=1 --',条件失效,全表泄露。
常见错误现象:ORA-00900: invalid SQL statement 或查出不该查的数据,但更危险的是查不出错、却悄悄越权。
- 所有来自外部(HTTP参数、表单、文件读取)的值,一律不参与字符串拼接
- 哪怕只是拼表名或列名,也要走白名单校验或
DBMS_ASSERT,不能靠正则“大概过滤” -
EXECUTE IMMEDIATE后面的 SQL 字符串必须是静态字面量或由可信变量组成(如配置常量)
绑定变量在 EXECUTE IMMEDIATE 中怎么写才生效
绑定变量不是“加个冒号”就行——位置、数量、类型、顺序都得严丝合缝。Oracle 不会自动推导类型,:x 在语句里出现几次,就要传几个值;传了 3 个值但语句里只有 2 个 :x,直接报 ORA-01008: not all variables bound。
使用场景:增删改查任意 DML,包括带 RETURNING 的语句。
- 用命名绑定(
:name)比位置绑定(:1)更安全,避免顺序错位 - 输入值类型要和目标列兼容,比如给
NUMBER列传字符串,Oracle 可能隐式转换失败,建议显式TO_NUMBER()或用对应类型的变量接收 -
USING子句只支持输入参数;输出要用INTO或RETURNING INTO,且变量必须已声明
示例:
EXECUTE IMMEDIATE 'UPDATE emp SET salary = :sal WHERE id = :id' USING v_new_sal, v_emp_id;
DBMS_SQL 和 EXECUTE IMMEDIATE 绑定行为有啥实际差别
DBMS_SQL 更底层,适合极动态场景(比如列名、WHERE 条件数完全不确定),但它要手动开游标、定义列、绑定、执行、关游标,出错点更多;EXECUTE IMMEDIATE 简洁,但要求 SQL 结构相对固定。
性能影响:简单语句用 EXECUTE IMMEDIATE 快得多,因为硬解析少;DBMS_SQL 每次都走完整解析流程,还容易因未关闭游标导致 ORA-01000: maximum open cursors exceeded。
- 95% 的动态需求用
EXECUTE IMMEDIATE+ 绑定变量就够了,别为“看起来更灵活”选DBMS_SQL - 如果真要用
DBMS_SQL,必须用DBMS_SQL.CLOSE_CURSOR显式关闭,不能依赖异常处理自动清理 -
DBMS_SQL.BIND_VARIABLE对CLOB/BLOB支持有限,大文本优先走EXECUTE IMMEDIATE+USING
存储过程中调用动态SQL,权限和定义者权限怎么不踩坑
动态 SQL 的执行权限在运行时检查,不是编译时。如果过程用 DEFINER'S RIGHTS(默认),它以拥有者身份执行,可能绕过调用者的对象权限;但若用 INVOKER'S RIGHTS,又可能因调用者没权限而失败——这不是绑定变量问题,但会掩盖注入漏洞。
常见错误现象:测试环境跑通,上线后报 ORA-00942: table or view does not exist,其实是权限模型没对齐。
- 不要在动态 SQL 里硬编码用户名或 schema 名(如
'SELECT * FROM scott.emp'),用USER或参数传入 schema,并校验是否在白名单中 - 如果过程必须访问其他用户的表,显式授予
SELECT权限,而不是靠DEFINER'S RIGHTS隐式越权 - 绑定变量本身不解决权限问题,但它让权限检查聚焦在“谁有权执行这条语句”,而不是“谁拼出了这条语句”
真正难的不是写对绑定语法,而是把“哪些值算外部输入”想全——比如从另一张配置表里查出来的字段名、从 UTL_HTTP 读的 JSON 字段、甚至系统时间格式化后的字符串,只要没经过可信源验证,就得当潜在注入点对待。

















