aiomysql的execute()不能直接拼接字符串以防SQL注入,因预处理需用%s占位符配合参数元组/列表,由数据库驱动层绑定;表名、列名等SQL结构须白名单校验,事务中须显式rollback。

为什么 aiomysql 的 execute() 不能直接拼接字符串?
因为 aiomysql 默认不解析 SQL 中的占位符(如 %s),如果你把用户输入直接用 f-string 或 .format() 拼进 SQL 字符串里,哪怕用了 execute(),照样被注入。比如:await cur.execute(f"SELECT * FROM users WHERE name = '{name}'") —— 这种写法完全绕过了预处理机制。
真正起作用的是数据库驱动层对参数的绑定,不是 Python 字符串操作。MySQL 协议要求客户端把 SQL 模板和参数分开发送,服务端在执行前才做类型检查和转义。
-
aiomysql只在使用%s占位符 + 参数元组/列表时,才会触发真正的预处理流程 -
%s是唯一被支持的占位符,:name或{name}会报错或静默失效 - 参数必须是
tuple或list,单个值要写成(value,),不是(value)
execute() 和 executemany() 的参数传递方式差异
两者都支持预处理,但传参结构不同,容易混淆导致报错或逻辑错误。
-
execute(sql, (val1, val2)):第二个参数是单个元组,对应一条语句执行 -
executemany(sql, [(val1,val2), (val3,val4)]):第二个参数是元组列表,每项对应一次执行 - 如果误把单条数据写成
[(val1, val2)]传给execute(),会触发TypeError: not all arguments converted -
executemany()不支持命名参数,只认位置占位符%s
示例正确用法:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
await cur.execute("INSERT INTO logs (msg, level) VALUES (%s, %s)", ("login failed", "ERROR"))
await cur.executemany("UPDATE users SET status = %s WHERE id = %s", [("active", 1), ("inactive", 2)])WHERE 条件中多个参数或动态字段怎么安全处理?
预处理不能用于表名、列名、排序方向(ASC/DESC)或 IN 子句长度变化——这些属于 SQL 结构,不是数据值,数据库不允许绑定。
- 列名/表名必须白名单校验后硬编码,例如:
if field not in ("name", "email", "created_at"): raise ValueError -
IN列表需动态生成占位符:"WHERE id IN ({})".format(",".join(["%s"] * len(ids))),再传ids元组 - 排序方向只能用条件判断:
order_sql = "ORDER BY created_at DESC" if desc else "ORDER BY created_at ASC" - 不要试图用
%s绑定列名,MySQL 会报1064 You have an error in your SQL syntax
事务中预处理语句的常见陷阱
预处理本身不保证原子性,出错时未提交的语句可能部分生效,尤其在批量操作中容易漏掉回滚。
- 所有预处理语句必须在同一个
Connection对象内执行,跨连接无法回滚 - 捕获
aiomysql.Error后必须显式调用conn.rollback(),否则连接可能处于脏状态 - 避免在循环中反复调用
execute()而不批量提交——性能差且增加事务锁持有时间 - 注意
autocommit=False是默认行为,但某些连接池配置可能覆盖它,建议显式设置
关键点在于:预处理只是防注入的第一步,事务控制、错误恢复、连接生命周期管理,一样不能松懈。

















