直接拼接字符串执行SQL必然导致SQL注入,唯一安全方式是用USING绑定变量;IN子句需用预定义占位符或集合类型处理;绑定变量还需注意类型匹配以防性能问题。
为什么直接拼接字符串执行SQL会出事
pl/sql里用 execute immediate 拼接用户输入,比如 'select * from users where name = ''' || user_input || '''',等于把数据库的执行权交给了外部数据。只要 user_input 是 ' or 1=1 --,整条语句就变成 select * from users where name = '' or 1=1 --'',绕过条件查全表。
这不是“可能被注入”,而是“只要没绑定,就一定可注入”。Oracle 不会帮你校验字符串里有没有恶意逻辑,它只认语法是否合法。
所有动态SQL都必须用 USING 绑定变量
动态SQL唯一安全的传参方式是 USING 子句,配合占位符 :var_name。Oracle 在解析阶段就固定了语句结构,运行时只代入值,不重解析。
- ✅ 正确写法:
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE deptno = :d' INTO cnt USING dept_id; - ❌ 错误写法:
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE deptno = ' || dept_id INTO cnt; - ⚠️ 注意:占位符名(如
:d)和USING后变量名(dept_id)可以不同,但顺序必须严格一致 - ⚠️ 如果有多个参数,
USING后按顺序列出变量,不能跳过;INTO只用于返回单行单列,多列用BULK COLLECT INTO
IN 子句怎么安全传多个值
IN 是常见痛点:绑定变量不能直接展开成 IN (:a, :b, :c),因为 Oracle 不允许在 USING 里传“列表”——你传进去的永远是一个标量。
解决方法只有两个:
- ✅ 预定义最大数量占位符,比如最多 10 个:
WHERE id IN (:1,:2,:3,...,:10),然后USING传 10 个变量,空余的用NULL或无效值占位(需确保业务逻辑能容忍) - ✅ 改用集合类型(如
sys.odcinumberlist)+TABLE()函数:WHERE id IN (SELECT COLUMN_VALUE FROM TABLE(:id_list)),USING传一个sys.odcinumberlist类型的集合变量 - ❌ 别用字符串拼出
IN (1,2,3)—— 这和拼表名一样危险
绑定变量不是万能解药:注意隐式类型转换和性能陷阱
用了 USING 只防了注入,不等于 SQL 就高效或正确。Oracle 会根据绑定变量的声明类型做隐式转换,有时导致索引失效。
- 例如:字段是
VARCHAR2(10),你传入NUMBER类型变量,Oracle 会把字段转成数字比对,全表扫描就来了 - 再如:日期字段用
TO_DATE(:dt, 'yyyymmdd'),而:dt是字符串,绑定后仍触发函数索引失效 - 建议:绑定变量类型尽量与字段类型一致;复杂格式转换尽量提到应用层处理,别塞进动态SQL里
最麻烦的是调试——错误信息里看不到实际代入的值,得靠 DBMS_OUTPUT 或跟踪 bind capture 查。这点容易被忽略,等上线出慢查询才回头翻日志。










