You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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注入风险。

修改你的代码

  1. 调整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
    # [...]
  1. 调用时分开传标识符和值:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 10:45:29