f-string和%格式化拼接sql会导致sql注入,因用户输入被当作sql语法执行;必须用参数化查询隔离结构与数据,表名列名等需白名单校验。

为什么不能用 f-string 或 % 格式化拼接 SQL
直接把用户输入塞进 SQL 字符串里,比如 f"SELECT * FROM users WHERE name = '{name}'",等于给 SQL 注入大开方便之门。Python 3.12 并没有改变这个底层风险——DB-API 规范依然要求靠参数化查询来隔离数据与语句结构。
常见错误现象包括:输入 ' OR '1'='1 后查出全部用户;或执行 '; DROP TABLE users; -- 导致数据被删(取决于数据库权限和驱动是否支持多语句)。
- 所有主流驱动(
sqlite3、psycopg、mysql-connector-python、pymysql)都只在execute()/executemany()的第二个参数中识别占位符,**不会**解析字符串内插值 -
%格式化和.format()同样危险,它们在 SQL 字符串生成阶段就完成了替换,驱动完全无法干预 - Python 3.12 新增的
sqlite3.Connection.execute()类型提示没改变行为,只是让 IDE 更容易发现你传了错类型
SQLite 中正确使用 ? 占位符的写法
SQLite 只支持问号位置占位符(?),不支持命名占位符(如 :name)用于普通查询(虽然 sqlite3 支持 :name,但仅限于 execute(),且必须配合字典;而 executemany() 仅接受序列)。
正确示例:
import sqlite3
<p>conn = sqlite3.connect("app.db")
cursor = conn.cursor()</p><h1>✅ 安全:参数作为独立元组传入</h1><p>username = "alice"
cursor.execute("SELECT id, email FROM users WHERE username = ?", (username,))</p><h1>✅ 批量查询也安全</h1><p>usernames = ["alice", "bob", "charlie"]
cursor.executemany("INSERT INTO logs (user) VALUES (?)", [(u,) for u in usernames])</p><h1>❌ 危险:哪怕加了括号,f-string 仍是字符串拼接</h1><h1>cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")</h1><p></p>
- 单元素元组必须带逗号:
(username,),否则 Python 当成括号表达式,传进去是字符串,驱动会报TypeError: parameters are of unsupported type - WHERE 子句多个条件时,占位符顺序必须和元组元素顺序严格一致:
WHERE a = ? AND b = ?对应(val_a, val_b) - 不能对表名、列名、ORDER BY 字段做参数化——这些属于 SQL 结构,需通过白名单校验 + 字符串拼接(如
if sort_col in ("created_at", "score"):)
PostgreSQL / MySQL 驱动中的命名参数差异
PostgreSQL(psycopg)和 MySQL(mysql-connector-python)支持命名参数,语义更清晰,但格式不兼容——别混用。
PostgreSQL(psycopg 3.x)用 %s(注意不是 %):
cursor.execute(
"SELECT * FROM products WHERE category = %s AND price <p>MySQL(mysql-connector-<a style="color:#f60; text-decoration:underline;" title="python" href="https://m.php.cn/zt/15730.html" target="_blank">python</a>)用 <code>%s</code>,但底层仍按位置匹配;若用 <code>dict</code>,则需用 <code>%(key)s</code>:</p><pre class="brush:php;toolbar:false;">cursor.execute(
"SELECT * FROM orders WHERE status = %(status)s AND total > %(min_total)s",
{"status": "shipped", "min_total": 50.0}
)
-
psycopg不支持%(name)s,强行用会抛ProgrammingError: named parameters not supported -
mysql-connector对%s和%(name)s都支持,但同一语句不能混用 - 参数类型会被自动转换(如 Python
datetime→ PostgreSQLTIMESTAMP),但 NULL 值要显式传None,不能传字符串"NULL"
最易被忽略的三个点
防御 SQL 注入不是“用了占位符”就万事大吉,这三个地方出错,前面全白干:
- 动态构建 WHERE 条件时,用字符串拼接字段名前,**必须**从预设列表中取值,而不是直接用用户传的
request.args.get("sort") - 调用存储过程时,如果传参方式是
CALL proc(?, ?),那没问题;但如果写成CALL proc('{user_input}'),照样中招 - 日志记录 SQL 语句本身时,别把原始参数元组直接
str()打印——里面可能含敏感信息;应单独记录参数类型和长度,或脱敏后记录
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











