SQLAlchemy的execute()不能直接拼接字符串,因为拼接后字符串被当作完整SQL字面量执行,绕过参数化绑定,导致SQL注入;必须使用text()配合命名参数(如:name)并传入params字典。

SQLAlchemy的execute()方法为什么不能直接拼接字符串
直接用f"SELECT * FROM users WHERE name = '{name}'"传给execute(),哪怕用了text()包装,也等于把用户输入当SQL字面量执行——这和原始SQL注入无异。SQLAlchemy不解析字符串里的占位符,text("...")只是告诉它“这是原生SQL”,不自动做参数绑定。
常见错误现象:sqlalchemy.exc.ProgrammingError: (psycopg2.errors.SyntaxError) syntax error at or near "admin"(实际是拼接后语法崩了),或更隐蔽地查出不该查的数据。
- 必须用
text()配合命名参数(:name)或位置参数(:1),再通过params=...传值 - 命名参数更安全、可读性高,推荐统一用
:key形式 - 避免混合使用:不要在
text()里写%s或{},那是旧式或格式化逻辑,不触发SQLAlchemy绑定
ORM查询中filter()和filter_by()的区别与风险点
filter_by()只支持简单等值匹配(filter_by(username='alice')),底层走的是安全的参数化查询;而filter()支持复杂表达式,但若误用字符串拼接(如filter("username = '" + name + "'")),立刻失效。
使用场景:动态字段名查询(比如根据用户选择的列排序)容易踩坑——getattr(User, sort_field)是安全的,但"User." + sort_field是危险的。
-
filter_by()只能用于已知字段的等值查询,不能传变量名字符串 -
filter()应始终用对象属性和运算符:User.username == name,而不是字符串 - 需要动态字段时,用
getattr(User, field_name)获取列对象,再参与表达式 - 绝对不要用
eval()或exec()拼接查询条件
原生SQL查询必须用text() + params组合
即使你确信要写原生SQL,SQLAlchemy也只认text()定义的占位符 + 外部传参这一种安全路径。任何绕过它的尝试(比如用Python格式化再交给execute())都会让防护形同虚设。
from sqlalchemy import text
# ✅ 正确:占位符+params
stmt = text("SELECT * FROM users WHERE status = :status AND age > :min_age")
result = conn.execute(stmt, {"status": "active", "min_age": 18})
<h1>❌ 错误:字符串拼接</h1><p>stmt = text(f"SELECT * FROM users WHERE status = '{status}'")</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" class="overflowclass">macOS UI 自动化技能包 —— 用 Python 控制你的 Mac</a>
<p class="overflowclass">让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript
系统要求
macOS 14+ (Sonoma)
Python 3.9+
需要开启:辅助功能 + 屏幕录制权限</p>
</div>
<a rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>❌ 错误:用%s,SQLAlchemy不识别</h1><p>stmt = text("SELECT * FROM users WHERE status = %s")</p>
- PostgreSQL/MySQL驱动本身支持参数化,SQLAlchemy在此之上做了统一抽象,但前提是它得知道哪些是参数
- 如果查询含多个同名参数(如
:id出现两次),用字典传参即可,SQLAlchemy自动复用 - 批量插入时,
execute(text(...), [dict1, dict2])仍安全,无需手写循环
预编译语句(compile())不是防注入手段
有人看到stmt.compile(compile_kwargs={"literal_binds": True})生成的SQL很像真实执行语句,就误以为“编译完就安全了”。其实这只是调试用的字符串快照,literal_binds=True甚至会把参数硬编码进去——这恰恰是注入的源头。
性能影响:启用literal_binds会禁用数据库端的查询计划缓存,且丧失类型转换能力(比如把True转成BOOLEAN或TINYINT)。
-
compile()只用于日志、调试、生成迁移脚本,绝不用于实际执行 - 真正执行必须走
execute()+ 参数字典,让驱动完成底层绑定 - ORM中
str(query.statement)同理,只是文本快照,不参与执行流程
SQL注入防御的关键不在“怎么写SQL好看”,而在于是否让数据库驱动接管参数序列化。SQLAlchemy所有安全机制都围绕这一点设计,一旦脱离params、==、filter_by()这些显式契约,就退回裸字符串风险区。最常被忽略的是动态字段和排序场景——那里没有魔法,只有getattr()和getattr(User, field).desc()这种机械但确定的安全写法。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










