最有效方式是使用参数化查询,原理是分离sql结构与数据,通过预处理语句绑定参数避免输入被解析为代码;拼接字符串(如f-string)会导致注入,因输入直接进入sql语法;占位符写法依驱动而异,但机制统一;表名、字段名等sql结构部分需白名单校验。

Python防止SQL注入最有效的方式是使用参数化查询,核心原理是将SQL语句结构与数据内容严格分离——数据库驱动先编译预处理语句(prepared statement),再把用户输入作为纯数据绑定执行,彻底避免输入被解析为SQL代码。
为什么拼接字符串必然危险
直接用f-string或+拼接用户输入,会让数据库把输入当作SQL语法的一部分。例如:
username = "admin' --"
query = f"SELECT * FROM users WHERE name = '{username}'"
实际执行变成:SELECT * FROM users WHERE name = 'admin' --',注释掉后续校验逻辑,直接绕过登录。
参数化查询的两种主流写法
不同数据库驱动支持的占位符不同,但机制一致:驱动自动转义并以二进制方式传参,不经过SQL解析器。
-
SQLite / PyMySQL(%s风格):用%s占位,参数用元组传入
cursor.execute("SELECT * FROM users WHERE age > %s AND city = %s", (25, "Beijing")) -
psycopg2(%s或命名风格):支持%s或%(name)s
cursor.execute("SELECT * FROM users WHERE status = %(s)s", {"s": "active"})
哪些操作不能靠参数化?需要额外过滤
参数化只适用于值(value),无法保护表名、字段名、ORDER BY子句、LIMIT数量等SQL结构部分。这些必须白名单校验或正则严格限制:
立即学习“Python免费学习笔记(深入)”;
- 排序字段:只允许["id", "name", "created_at"]中的字符串
- 动态表名:用字典映射,如{"user": "tbl_user_v1", "order": "tbl_order_2024"}
- LIMIT数量:强制转为整数并限定范围,如max(1, min(100, int(n)))
其他关键防护习惯
参数化是基础,还需配合以下实践才能形成完整防线:
- 最小权限原则:数据库账号仅授予必要表的SELECT/INSERT权限,禁用DROP/EXECUTE
- 关闭详细错误提示:生产环境避免暴露SQL结构,用通用错误页代替数据库原错信息
- ORM也需谨慎:Django ORM默认防注入,但extra()、raw()等接口仍可能触发拼接,务必检查










