SQL预编译通过参数化查询实现结构与数据分离,先编译执行计划再绑定参数,既防范SQL注入又提升重复查询性能;需用占位符(如?、$1)传参而非字符串拼接,表名等动态部分须白名单校验。

SQL预编译机制通过参数化查询实现,核心是把SQL语句结构和数据分离,让数据库先编译执行计划、再绑定参数运行,从而同时防范SQL注入、提升重复查询性能。
怎么写预编译SQL(以常见语言为例)
不是拼接字符串,而是用占位符(如red">?或:name),再单独传参:
- Java(JDBC):用PreparedStatement,? 占位,调用setString(1, value)等方法赋值
- Python(sqlite3 / pymysql):用(?)或%s占位,cursor.execute(sql, (val1, val2))
- Go(database/sql):用$1, $2(PostgreSQL)或?(MySQL),db.Query(sql, arg1, arg2)
为什么能防SQL注入
因为参数值不参与SQL语法解析——数据库只把占位符当“数据”处理,不会当作代码执行。即使用户输入' OR '1'='1,也会被当做一个普通字符串字面量,无法改变原有SQL逻辑。
⚠️ 注意:拼接表名、字段名、排序方向(ASC/DESC)等不能用参数化,需白名单校验或正则严格过滤。
为什么能提升性能
- 相同结构的SQL首次执行后,数据库会缓存执行计划(如MySQL的query cache已弃用,但prepared statement cache仍有效;PostgreSQL也复用执行计划)
- 省去每次解析、语法检查、生成执行计划的开销,尤其对高频小查询效果明显
- 网络传输更少(SQL模板发一次,后续只传参数)
实际使用要注意什么
- 连接关闭前记得close() PreparedStatement,避免句柄泄漏
- 批量操作优先用addBatch() + executeBatch(),比循环单条预编译更快
- 不要为“一次性的简单查询”强行预编译——收益小,反而增加代码复杂度
- ORM框架(如MyBatis、Hibernate)默认支持预编译,确认配置项如useServerPrepStmts=true(MySQL JDBC)已开启











