为什么 SQL 预编译(PreparedStatement)能防止 SQL 注入,并从多个层面展开分析。
一、什么是 SQL 注入?
SQL 注入是一种攻击方式,攻击者通过将恶意 SQL 语句注入输入参数中,使原本正常的查询逻辑被篡改,执行非预期的 SQL 操作(如读取全部数据、绕过登录验证、删除表等)。
示例(未预编译):
String name = "a' OR '1'='1";
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
拼接后 SQL 为:
SELECT * FROM users WHERE name = 'a' OR '1'='1'
这就返回了所有用户,而不是指定 name 的记录。
二、什么是 SQL 预编译(PreparedStatement)?
SQL 预编译指的是:
数据库会 先编译 SQL 模板(不带具体值,仅保留参数占位符 ?),
然后在执行时再将参数值 安全地绑定到这些占位符上。
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
ps.setString(1, name); // 即使 name 是恶意构造的也不会被解析为 SQL 语法
三、为什么预编译能防止注入?
✅ 核心原理:
参数值不会参与 SQL 语法解析
预编译后 SQL 模板被“锁定”,参数仅作为数据处理,数据库不会再将参数解析成 SQL 语句的一部分。
四、举个对比实验
❌ 非预编译(会注入):
String userInput = "1 OR 1=1";
String sql = "SELECT * FROM users WHERE id = " + userInput;
// 实际执行 SQL:SELECT * FROM users WHERE id = 1 OR 1=1
✅ 预编译(防注入):
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setString(1, "1 OR 1=1");
// 实际执行:SELECT * FROM users WHERE id = '1 OR 1=1' → 字符串字面量,不参与 SQL 判断
五、数据库如何处理预编译语句?
以 MySQL 为例:
客户端发出 SQL 模板(如 SELECT * FROM users WHERE id = ?)
MySQL 生成并缓存执行计划(execution plan)
再接收参数(如 id = '1 OR 1=1')
将参数 当作字面值 插入表达式中,不再进行 SQL 解析
执行安全查询
六、总结为一句话:
预编译 SQL 是将“代码”与“数据”分离的机制,从根本上杜绝了 SQL 注入的可能。
评论区