?占位符是DuckDB唯一推荐的参数化方式,因其将参数作为独立类型传入执行器,完全脱离SQL解析,杜绝注入且支持计划复用;须传元组或列表,不支持命名参数。

直接用 ? 占位符 + execute() 传参,是唯一安全、可靠、零配置的参数化方式。其他字符串拼接、f-string、.format() 或手动转义,哪怕只用于数字或枚举值,都可能在边界场景被绕过。
为什么 ? 占位符是 DuckDB 唯一推荐的参数化方式
DuckDB 的参数化机制在底层与查询计划强绑定:占位符 ? 不是简单替换,而是让引擎将参数值作为独立数据类型传入执行器,完全脱离 SQL 解析阶段。这意味着:
- 用户输入永远不会进入 SQL 语法树,
1 OR 1=1这类 payload 会被当作字面量字符串处理 - 类型自动推导(如
?在WHERE id = ?中被识别为整数)能防止隐式类型转换引发的逻辑绕过 - 同一语句多次调用时,执行计划可复用,性能不打折
- 不支持命名参数(如
:name或%s),用错会直接报Invalid input syntax
execute() 必须传元组或列表,不能传单个值
常见错误是写成 con.execute("SELECT * FROM t WHERE id = ?", 123) —— 这会触发 TypeError: parameters must be a list or tuple。DuckDB 要求显式容器,哪怕只有一个参数:
- ✅ 正确:
con.execute("SELECT * FROM t WHERE id = ?", (123,))或[123] - ✅ 正确(多个参数):
con.execute("SELECT * FROM t WHERE a = ? AND b = ?", (1, "foo")) - ❌ 错误:
con.execute("...", 123)、con.execute("...", [123,])(尾随逗号缺失导致变成 int) - ⚠️ 注意:空元组
()仅用于无参查询,如SELECT 1
哪些场景容易漏掉参数化?
最常被忽略的是表名、列名、排序字段、IN 子句长度不定的场景 —— 这些无法用 ?,必须走白名单校验或重构逻辑:
- 表名/列名:用预定义枚举或正则校验(如
re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', user_input)) -
ORDER BY字段:限定为["created_at", "score", "name"]等固定选项 -
IN (?, ?, ?)动态长度:先生成对应数量的?占位符,再拼参数列表,例如placeholders = ','.join(['?'] * len(ids)),然后execute(f"SELECT * FROM t WHERE id IN ({placeholders})", ids) - 不要尝试用
str.replace()清洗 SQL 片段 —— 绕过方式太多,不可信
真正难的不是写对一个 ?,而是所有动态拼接点都得过一遍白名单或参数化检查。哪怕 99% 的查询用了 ?,剩下 1% 的表名拼接没校验,整个防护就形同虚设。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











