mysql.connector 8.0.29的paramstyle是否存在参数风格上报错误?
使用mysql-connector-python 8.0.29版本时,mysql.connector.paramstyle的返回值为'pyformat'。
根据PEP 249对paramstyle的定义,在目标表已存在的前提下,向execute()方法传入参数['Python', 10]执行下述插入SQL,预期应当合法:
INSERT INTO lang(name, score) VALUES (%(c1)s, %(c2)s)
实际执行时返回如下错误:
mysql.connector.errors.ProgrammingError: Not all parameters were used in the SQL statement
该错误说明%(<name>)s格式并未被驱动识别为值占位符。
而将插入语句修改为下述形式后,使用相同参数调用即可成功执行:
INSERT INTO lang(name, score) VALUES (%s, %s)
但根据PEP 249文档说明,%s属于'format'参数风格,不属于'pyformat'风格。
因此产生核心疑问:mysql.connector.paramstyle是否错误上报了自身支持的参数风格?如果不存在上报错误,是哪部分理解出现了偏差?
问题背景
正在将内部自研SQL库从Java移植到Python,作为通用数据库适配库,需要保持对关系型数据库厂商的中立性,因此需要根据驱动上报的paramstyle,生成符合PEP 249规范的各类查询、更新语句。
复现代码
# paramstyle.py import mysql.connector conf = { 'user': 'root', 'password': 'password', 'host': 'localhost', 'port': 3306, 'database': 'test' } c = mysql.connector.connect(**conf) with c.cursor() as csr: csr.execute('DROP TABLE IF EXISTS lang') csr.execute('CREATE TABLE lang(name VARCHAR(50), score INTEGER)') csr.execute('INSERT INTO lang(name, score) VALUES (%(c1)s, %(c2)s)', ['Python', 10]) #csr.execute('INSERT INTO lang(name, score) VALUES (%s, %s)',) # ['Python', 10]) c.commit() c.close()
执行报错信息:
$ python3 paramstyle.py Traceback (most recent call last): File "paramstyle.py", line 13, in <module> csr.execute('INSERT INTO lang(name, score) VALUES (%(c1)s, %(c2)s)', File "/usr/local/lib/python3.8/site-packages/mysql/connector/cursor.py", line 559, in execute raise errors.ProgrammingError( mysql.connector.errors.ProgrammingError: Not all parameters were used in the SQL statement
附加疑问:为什么PEP 249要允许五种不同的参数占位符实现方式?仅保留一种标准难道不足以满足需求吗?这种设计是否会提升开发数据库无关客户端代码的复杂度?
核心误解点
mysql.connector.paramstyle返回'pyformat'没有任何上报错误,问题出在对pyformat参数匹配规则的理解偏差:
PEP 249定义的pyformat风格,完全复用Python原生%字符串格式化的逻辑,同时支持两种传参模式:
- 位置匹配:占位符写
%s,传入列表、元组这类序列类型参数,按顺序一一对应占位符 - 命名匹配:占位符写
%(key名)s,传入的参数必须是字典,按key名匹配对应占位符
你代码里用了%(c1)s、%(c2)s这种命名占位符,结果传了列表['Python', 10],驱动在参数里根本找不到c1、c2这两个键,当然会判定占位符没绑定到有效参数,抛出"Not all parameters were used"的错误。
要验证命名占位符的支持很简单,把传参改成对应字典就能正常执行:
csr.execute( 'INSERT INTO lang(name, score) VALUES (%(c1)s, %(c2)s)', {'c1': 'Python', 'c2': 10} )
你之前用%s加列表能跑通,也不是驱动偷偷支持了format风格——位置传参本来就是pyformat原生支持的能力,完全符合PEP 249规范。
关于PEP 249允许多种参数风格的原因
PEP 249最早在2001年发布,出台前Python生态已经有大量各数据库的第三方驱动,这些驱动早就形成了自己的参数占位符习惯:比如部分驱动直接沿用数据库原生的$1、$2数字序号占位符(即numeric风格),有的用ODBC系通用的?占位符(即qmark风格),还有的直接照搬Python自身的字符串格式化逻辑。
如果当时强制要求所有驱动统一使用同一种占位符,所有存量驱动都要做不兼容的重构,迁移成本极高,规范根本推不下去。PEP 249的核心优先级是先统一核心API的形态(比如统一连接、游标、执行、结果获取的方法约定),参数风格只做枚举收口,要求驱动明确上报自身支持的类型即可,属于典型的为了兼容存量做的折中设计。
这种设计确实会增加开发数据库无关通用库的复杂度——这也是为什么SQLAlchemy这类成熟的数据库中间层都会自己做一层参数转译,屏蔽不同驱动的参数风格差异,上层逻辑完全不需要感知底层驱动的paramstyle是什么。
内容的提问来源于stack exchange,提问作者Paul

