【已解决】Python MySQL: Not all parameters were used in the SQL statement

一、事故缘起

今天构造了一个超过 50 多个参数的 SQL 插入语句,在执行的时候提示 Not all parameters were used in the SQL statement,提示「SQL 语句中未使用所有参数」的异常,但是前前后后检查了 SQL 语句,发现每个参数都是与相应的字段一一对应的,类似于下面这样的代码块:

mydb = mysql.connect(...)
cursor = mydb.cursor()

queries = [(...), (...), (...)]  # 当然这几部分都是动态构造的
sql = "INSERT INTO `db`.`table`(`col01`, `col02`, `col03`) VALUES (%s, %s, %s)"
# 注意上条语句中 %s 和 Python 本身的字符串占位符重合,但其实不能混用
# 它可以用 ? 号代替,但是不能用 Python 的 %d, %f 等格式化符号代替
cursor.executemany(sql, queries)

cursor.close()
mydb.close()

在确定参数一个都没有少的情况下,开始猜测是系统截断了什么东西,然后在 StackOverflow 回答 的评论区找到了上述异常的解决方案,在声明数据库游标 cursor 的时候,直接加上参数 prepared=True 就搞定了,类似于下面的语句:

cursor = mydb.cursor(prepared=True)

接下来的文章就试着描述一下这个 prepared 预编译的作用吧,文章要点参考自 PyNative,加入了一些个人见解。

二、什么是参数化查询

参数化查询是在原有的查询语句中,用占位符填充各个参数,然后在执行的过程中,再将参数值传入的一种方法。这意味着参数化查询的被调用语句只被编译一次,但是可以重复传参。比如下面这条语句:

sql = "UPDATE `transaction` SET `quantity` = %s WHERE `customer_id` = %s"

在上述语句中,我们使用了 MySQL 中的 %s 占位符来传递参数值,当然这里用 ? 号或者关键词参数 %(customer_id)s 也是可以的,通过 execute(sql, data_tuple) 能够有效地防止 SQL 注入攻击,当然它有更多其他好处。

三、参数化查询的益处

参数化查询的特性主要有以下 4 点:

  1. 只编译一次:在默认的标准查询中,MySQL 在每次执行的时候都会编译一次语句。而在参数化查询中,被执行的查询语句只被编译一次,然后在内存中恭候传入参数进行调用,MySQL 能够直接执行预编译好的语句从而减少每次编译的时间;
  2. 加快执行速度:尤其是需要多次调用相同的 SQL 语句时,次数越多速度提升越明显,有点像正则表达式的 compile 预编译;
  3. 能够使用不同的数据执行相同的操作:假设你拥有几百行的数据要插入到数据库表中,使用参数化查询能够存入不同的字段值;
  4. 防止 SQL 注入攻击,有效提升数据安全。

四、如何使用参数化查询

我们使用 mysql.connection.cursor(prepared=True) 声明游标,就可以使用参数化查询执行预编译语句了,它本身是一个 MySQLCursorPrepared 类,继承自 MySQLCursor 类对象。

from mysql import connector

connection = connector.connect(
    host="localhost",
    port="3306",
    user="MoonYear530",
    password="StanleyBlog",
    database="query_test"
)
cursor = connection.cursor(prepared=True)
# 上一条语句也可以拟写如下:
# cursor = connection.cursor(cursor_class=MySQLCursorPrepared)

这样,在接下来的每一次调用中,传入的参数虽然不同,但是在准备执行时发现 SQL 语句是一样的时候,就会跳过编译了。

五、使用参数化查询更新数据的示例

from mysql import connector
from mysql.connector import Error

try:
    connection = connector.connect(
        host="localhost",
        port="3306",
        user="MoonYear530",
        password="StanleyBlog",
        database="query_test"
    )  # 创建数据库连接
    cursor = connection.cursor(prepared=True)  # 声明数据游标

    sql = """UPDATE `transaction`
             SET `update_date` = %s, `quantity` = %s
             WHERE `customer_id` = %s"""  # 被调用 SQL 语句
    data_tuple = ("2020-09-20", 16530, "SZ0198")  # 传入的参数元组

    cursor.execute(sql, data_tuple)  # 执行 SQL 语句
    connection.commit()  # 提交事务
    print("恭喜,数据更新成功!")

except Error as error:
    connection.rollback()  # 执行失败,数据回滚
    print(f"参数化查询执行异常:{error}")

finally:
    if connection.is_connected():
        cursor.close()  # 关闭数据游标
        connection.close()  # 关闭数据库连接
        print("老铁,数据库连接已关闭。")

以上代码只是一个简短的示例,需要按照工程内容适当修改,祝一切顺利,代码无 Bug,身体倍棒,吃嘛嘛香~

posted @ 2020-09-20 09:17  MoonYear530  阅读(2226)  评论(0编辑  收藏  举报