不能用f-string或%拼接用户输入到sql中,否则导致sql注入;必须用%s占位符配合元组传参,且%s仅适用于值而非表名、列名等sql结构。

为什么不能用f-string或%格式化拼接SQL
直接把用户输入塞进SQL字符串里,等于给SQL注入开绿灯。psycopg2 本身不校验SQL结构,它只负责把字符串发给PostgreSQL——只要字符串里混进了恶意的 ' OR 1=1 --,就可能查出全表数据、删库、甚至执行系统命令(取决于数据库权限)。
常见错误写法:
# 危险!绝对不要这么写
user_id = "123; DROP TABLE users;"
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # 直接拼接
cursor.execute("SELECT * FROM users WHERE id = %s" % user_id) # 同样危险
这类操作绕过了参数绑定机制,psycopg2 完全无法干预,数据库会原样解析执行。
正确用元组传参:只允许位置占位符 %s
psycopg2 的参数化查询只支持一种占位符:%s(注意不是Python的字符串格式化,只是约定符号),且必须用元组或列表传参。即使只有一个参数,也必须是单元素元组,末尾加逗号。
- ✅ 正确:参数为元组
(value,)或列表[value] - ❌ 错误:传入单个值
value、字符串"value"、或没逗号的元组(value)(这会被当成括号包裹的表达式,实际是int类型)
示例:
# ✅ 安全写法
user_id = 123
cursor.execute("SELECT name, email FROM users WHERE id = %s", (user_id,)) # 注意逗号
<h1>✅ 多参数也是元组</h1><p>name = "Alice"
age = 30
cursor.execute("INSERT INTO users (name, age) VALUES (%s, %s)", (name, age))</p><h1>❌ 运行时报错:not all arguments converted</h1><p>cursor.execute("SELECT * FROM users WHERE id = %s", user_id) # 少了括号和逗号
</p>
哪些地方不能用 %s 占位符
%s 只能替换**值(value)**,不能替换标识符(如表名、列名、ORDER BY 字段、LIMIT 数字等)。这些属于SQL语法结构,数据库在解析阶段就需要确定,而参数绑定发生在执行阶段。
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
- ❌ 表名、列名、关键字(如
ASC/DESC)不能参数化 - ❌
LIMIT和OFFSET的数字虽是值,但 PostgreSQL 不允许用%s绑定(会报TypeError: argument of type 'int' is not iterable或语法错误)
应对方式只有两个:
- 白名单校验 + 字符串拼接(仅限完全可控的输入,比如固定排序字段枚举)
- 使用
psycopg2.sql模块构造安全的动态SQL(需显式转义)
例如限制排序字段:
from psycopg2 import sql
<p>allowed_sorts = {"name", "email", "created_at"}
sort_by = "name"
if sort_by not in allowed_sorts:
raise ValueError("Invalid sort field")</p><p>query = sql.SQL("SELECT * FROM users ORDER BY {}").format(
sql.Identifier(sort_by)
)
cursor.execute(query)
</p>
execute() vs executemany():批量插入别用错
单条语句多次执行用 execute();批量插入/更新多行数据,必须用 executemany() 并传入**二维序列**(列表套元组),否则效率极低,还可能触发连接超时。
- ✅
executemany("INSERT ... VALUES (%s, %s)", [(v1,v2), (v3,v4)]) - ❌ 把一维列表传给
executemany,比如[(v1,v2), (v3,v4)]写成[v1,v2,v3,v4]
注意:executemany 不支持返回结果集(如 SELECT),只适用于 INSERT/UPDATE/DELETE。
另外,如果批量数据量极大(数万行以上),考虑用 copy_from() 或 execute_batch()(psycopg2.extras 提供),它们比 executemany 更快且内存更友好。
最易被忽略的一点:事务控制。参数化本身不自动开启事务,INSERT 或 UPDATE 后必须显式调用 conn.commit(),否则变更不会持久化——这点和直觉相悖,尤其从SQLite切换过来的人容易踩坑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










