不能直接用f-string拼接sql,因其完全绕过参数化机制,导致sql注入风险;占位符必须匹配驱动语法(如sqlite用?、psycopg2用%s),且仅适用于值而非表名/列名,后者需白名单校验。

为什么不能直接用 f-string 拼接 SQL 查询
直接用 f"SELECT * FROM users WHERE name = '{name}'" 是最典型的 SQL 注入入口。数据库驱动不会识别这种字符串里的占位符,所有内容都被当作原生 SQL 执行。哪怕你对输入做了 str.replace("'", "''") 或其他手工转义,也极易遗漏边界情况(比如嵌套引号、Unicode 分隔符、注释符 -- 或 #),且不同数据库的转义规则还不一致。
read_sql_query 的参数化只支持特定占位符格式
read_sql_query 本身不处理参数,它把 SQL 和参数一并交给底层 DBAPI(如 psycopg2、sqlite3、pymysql)执行。这意味着占位符必须匹配对应驱动的语法:
- SQLite 和 PyMySQL 用
?(问号):"SELECT * FROM logs WHERE level = ? AND ts > ?" - psycopg2(PostgreSQL)用
%s:"SELECT * FROM events WHERE category = %s AND created_at > %s" - 不要混用,比如在 psycopg2 里写
?会报错TypeError: not all arguments converted during string formatting
参数必须以元组或列表传入 params 参数,不能是字典(除非驱动明确支持命名参数,但 read_sql_query 的通用接口不保证兼容)。
表名、列名、排序字段无法参数化,必须白名单校验
SQL 标准不允许对标识符(如表名、列名、ORDER BY 字段)使用参数占位符。下面这行代码会出错:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
read_sql_query("SELECT * FROM ? WHERE id = ?", con, params=["users", 123])
因为 ? 只能代入值,不能代入标识符。正确做法是提前定义允许的字段列表,用 in 判断:
- 安全:
if sort_col in ["created_at", "score", "status"]:再拼进 SQL - 危险:
f"ORDER BY {user_input}"—— 即使加了strip()也拦不住"score; DROP TABLE users;" - 动态表名同理,必须映射到预设键值,例如
table_map = {"log": "app_logs", "event": "user_events"}
使用 SQLAlchemy 引擎时注意方言差异
如果你用的是 sqlalchemy.create_engine 创建的连接,read_sql_query 默认按该引擎配置的方言解析参数。但要注意:
- PostgreSQL + psycopg2:用
%s,参数顺序必须严格匹配 - SQLite + built-in driver:用
?,支持位置参数,不支持命名参数(:name会报错) - 如果换用
pd.read_sql并传入text()对象,才能用命名参数,但这就脱离了纯read_sql_query的简单路径
最稳妥的做法是:查清你实际使用的 DBAPI 驱动文档,然后统一用其推荐的占位符,别依赖 pandas 的“自动适配”——它并不存在。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










