使用Python MySQL connector时出现'?'语法错误,求来源及解决
问题分析与解决
关于神秘的?符号
你代码里看不到?,但报错里出现是因为:当你开启prepared=True游标时,mysql.connector会自动把SQL里的%s转换成MySQL预编译语句专用的?占位符——这是驱动内部的转换逻辑,所以你自己写的代码里看不到,但传给数据库的SQL已经变成了SELECT * FROM ?,这直接违反了SQL语法,因为MySQL不允许用预编译占位符替代表名。
核心问题:预编译占位符不能用来替换表名
预编译语句的%s(或转后的?)只能用来填SQL里的具体值(比如WHERE id = %s里的ID数值),绝对不能用来替换表名、列名这类数据库对象的名字。原因很简单:预编译是先让数据库解析SQL的结构,表名属于结构的一部分,没法在预编译阶段动态替换。
解决办法
如果要动态指定表名,得手动转义表名后拼接SQL,不能用参数绑定。mysql.connector提供了escape_identifier方法来安全转义这类标识符,避免SQL注入风险。
修改你的代码
- 调整
query方法,区分标识符转义和值绑定:
class MySQL: # [...] def query(self, query: str, values: tuple = (), identifiers: tuple = ()) -> MySQLCursorPrepared | MySQLCursorBuffered: # 先处理表名/列名这类标识符的转义与替换 if identifiers: escaped_ids = tuple(self.__conn.escape_identifier(id) for id in identifiers) query = query % escaped_ids try: if values: self.__cursor = self.__conn.cursor(prepared=True) self.__cursor.execute(query, values) else: self.__cursor = self.__conn.cursor(buffered=True) self.__cursor.execute(query) return self.__cursor # 补全你的异常处理 except Exception as e: # 这里可以加日志或重新抛出异常 raise # [...]
- 调用时分开传标识符和值:
def test() -> None: with MySQL(database="lockfile") as conn: # 表名作为标识符传入,不需要用值绑定的方式 res = conn.query("SELECT * FROM %s", identifiers=("fileslocked",)) print(f"Rows: {res.rowcount}")
重要提醒
- 绝对不能直接把用户输入的表名/列名拼进SQL,必须用
escape_identifier转义,否则会有SQL注入风险。 - 只有SQL里的具体数据值(比如查询条件里的字符串、数字)才用参数绑定(
values参数),这样才能发挥预编译的安全和性能优势。
内容的提问来源于stack exchange,提问作者Seintian
相关产品推荐
相关产品推荐

