psycopg3仅支持参数化值绑定,表名、列名、order by等sql结构必须用sql.identifier或白名单校验;%s和%(name)s只绑定值,不可用于结构拼接;动态结构须避免f-string拼接,批量操作需显式占位符,事务与连接需显式管理。

psycopg3 本身不支持参数化表名、列名或 ORDER BY 子句——这些必须白名单校验,否则参数绑定再严也挡不住注入。
psycopg3 的 %s 和 %(name)s 占位符都只绑定值,不绑定结构
你写 cursor.execute("SELECT * FROM users WHERE name = %s", ("admin' --",)) 是安全的,驱动会把 "admin' --" 当纯字符串转义后传入;但写 f"SELECT * FROM {table_name}" 或 "ORDER BY %s" 就直接崩了——%s 在 ORDER BY 后会被当作文本字面量,不是标识符。
-
psycopg3支持两种风格:%s(位置式)和%(key)s(命名式),二者底层机制一致,都只用于值绑定 - 所有用户输入的「值」(如
WHERE age > ?中的数字、IN (?, ?)中的列表项)必须走占位符,绝不能用f-string或+拼接 - 错误示例:
cursor.execute(f"DELETE FROM {table} WHERE id = {user_id}")—— 无论user_id是否转成 int,只要table是动态的,就已失守
动态表名/列名必须用白名单或 sql.Identifier
psycopg3 提供了 sql.Identifier 和 sql.SQL 组合来安全拼接 SQL 结构。它不做字符串替换,而是生成带引号的、经 PostgreSQL 语法校验的标识符。
- 正确做法:
from psycopg.sql import SQL, Identifier table_name = "users" query = SQL("SELECT * FROM {} WHERE status = %s").format(Identifier(table_name)) cursor.execute(query, ("active",)) - 白名单更轻量:
if table_name not in ["users", "orders", "logs"]: raise ValueError("Invalid table") - 别信“转义函数”:自己写
quote_ident()容易漏掉 Unicode 边界、嵌套引号等,sql.Identifier是唯一推荐路径
批量操作(executemany)的占位符必须与每行字段数严格匹配
executemany 不接受 VALUES %s 这种整体占位——它会把整个元组当一个参数,导致 ProgrammingError: the query has 1 placeholder but 7 parameters were passed。
- 必须显式写出每个字段的占位符:
"INSERT INTO t (a,b,c) VALUES (%s, %s, %s)" - 如果字段数不确定,用字符串生成:
"(%s)" * len(row)拼出括号内占位符串,再.join()成完整VALUES子句 - 数组参数可用于
IN:如WHERE id = ANY(%s)配合params=([1,2,3],),比循环执行高效且安全
事务边界和连接生命周期常被忽略
多语句操作(比如先删子表再删主表)若没包在事务里,失败时可能留下脏数据;而连接不 close 或 cursor 不清理,会拖慢连接池甚至触发 too many clients 错误。
- 显式
conn.begin()+try/except/finally管理 commit/rollback - 用上下文管理器最稳:
with conn.cursor() as cur:,自动 close - 别复用同一
cursor执行不同结构的查询——psycopg3 的 cursor 不是线程安全的,且预编译状态可能冲突
真正危险的从来不是「不知道用 %s」,而是以为用了 %s 就万事大吉,结果在 ORDER BY、TABLE、LIMIT 这些地方留了口子——这些地方连 sql.Identifier 都救不了乱来的逻辑。











