psycopg2中execute()参数必须是元组或字典,不可用列表;sqlite3仅支持?或:name占位符,不支持%s;ORM手写SQL仍需参数化;表名等结构部分须白名单校验。

psycopg2 中 execute() 的参数必须是元组或字典,不能是列表
很多人在用 psycopg2 时写成 cursor.execute("SELECT * FROM user WHERE id = %s", [user_id]),结果报 TypeError: not all arguments converted。这不是代码逻辑错,而是 psycopg2 明确要求参数必须是元组(哪怕只有一个值也要加逗号)或字典。
- ✅ 正确:
cursor.execute("SELECT * FROM user WHERE id = %s", (user_id,)) - ✅ 正确:
cursor.execute("SELECT * FROM user WHERE id = %(id)s", {"id": user_id}) - ❌ 错误:
cursor.execute("SELECT * FROM user WHERE id = %s", [user_id])
原因在于 psycopg2 的参数解析器只识别元组和字典结构,列表会被当作单个参数传入,导致占位符匹配失败。这个细节在调试时容易被忽略,但直接关系到参数化是否真正生效。
sqlite3 支持 ? 和 :name,但不支持 %s
如果你把 MySQL 或 PostgreSQL 的写法直接套到 sqlite3 上,比如 "SELECT * FROM users WHERE name = %s",会发现查询总返回空结果——不是语法错,而是 sqlite3 根本不认 %s 占位符。
- ✅ 只能用
?:cursor.execute("SELECT * FROM users WHERE name = ?", (name,)) - ✅ 或命名参数
:name:cursor.execute("SELECT * FROM users WHERE name = :name", {"name": name}) - ❌ 不支持
%s、%()s等任何 Python 字符串格式化符号
混用不同驱动的占位符语法是常见坑点,尤其在多数据库适配或迁移项目中。别依赖 IDE 的自动补全,得看文档确认当前驱动支持哪种。
立即学习“Python免费学习笔记(深入)”;
ORM 并不自动防所有注入,手写 SQL 片段仍危险
用 SQLAlchemy 或 Django ORM 就等于安全?不是。只要你在 ORM 里拼接用户输入进字符串,照样中招。
- ❌ 危险示例(Django):
User.objects.extra(where=[f"username = '{request.GET.get('u')}'"]) - ❌ 危险示例(SQLAlchemy):
session.execute(f"SELECT * FROM logs WHERE table_name = '{table}'") - ✅ 安全做法:所有用户输入都走参数化接口,哪怕在 ORM 里调用原生 SQL,也要用
.execute(text("..."), {"param": value})
ORM 的安全是“默认路径安全”,不是“绝对免疫”。一旦你绕过它的查询构造层,手动拼 SQL,防御就退回原点。
表名、字段名不能参数化,必须白名单校验
占位符只能用于值(WHERE 条件、INSERT 数据等),不能用于结构部分。比如动态切换表名:f"SELECT * FROM {table_name} WHERE id = ?" —— 这里 {table_name} 无论如何都不能用参数化解决。
- 必须提前定义合法范围:
ALLOWED_TABLES = {"users", "orders", "products"} - 运行时强校验:
if table_name not in ALLOWED_TABLES: raise ValueError("Invalid table name") - 字段名同理,排序字段、搜索字段都要进白名单,不能靠正则模糊过滤
这是最容易被跳过的环节:大家盯着值的安全,却放过了结构控制。而攻击者恰恰会从这里入手,比如通过 table_name=users; DROP TABLE users-- 实现语句截断。


















