无法使用Python游标从存储过程返回结果

16 投票
3 回答
22376 浏览
提问于 2025-04-17 18:34

出于某种奇怪的原因,我在一个Python测试应用中无法从调用存储过程的结果中获取任何数据。这个存储过程在MySQL 5.2.47中是这样的:

CREATE PROCEDURE `mytestdb`.`getperson` (IN personid INT)
BEGIN
   select person.person_id,
          person.person_fname,
          person.person_mi,
          person.person_lname,
          person.persongender_id,
          person.personjob_id
     from person
    where person.person_id = personid;
END

现在,我在使用Python 3.3的PyCharm时,调用这个存储过程似乎什么都取不回来。这段代码能让我得到想要的结果:

import mysql.connector

cnx = mysql.connector.connect(user='root', host='127.0.0.1', database='mytestdb')
cnx._open_connection()
cursor = cnx.cursor()

cursor.execute("select * from person where person.person_id = 1")
people = cursor.fetchall()

for person in people:
    print(person)

cnx.close()

但是这段代码用cursor.fetchall()或cursor.fetchone()...

import mysql.connector

cnx = mysql.connector.connect(user='root', host='127.0.0.1', database='mytestdb')
cnx._open_connection()
cursor = cnx.cursor()

cursor.callproc("getperson", [1])
people = cursor.fetchall()

for person in people:
    print(person)

cnx.close()

...却返回了“mysql.connector.errors.InterfaceError: 没有结果集可以获取。”使用cursor.execute()方法时还有另外一种奇怪的行为,如下所示...

import mysql.connector

cnx = mysql.connector.connect(user='root', host='127.0.0.1', database='mytestdb')
cnx._open_connection()
cursor = cnx.cursor()

cursor.execute("call getperson(1)")
people = cursor.fetchall()

for person in people:
    print(person)

cnx.close()

...因为它会出现“mysql.connector.errors.InterfaceError: 对于多个查询的语句,请使用cmd_query_iter”,接着是“mysql.connector.errors.InterfaceError: 执行多个语句时请使用multi=True”,尽管我实际上只返回一个查询结果,而不是多个结果集。难道MySQL的Python连接器把对存储过程的execute调用当成了双重查询?我该如何仅仅调用存储过程并获取我的结果?我真的不想在我的代码中使用动态SQL。提前感谢任何建议!

3 个回答

0

为什么不试试这样做呢?

cursor.callproc("getperson", ['1'])
8

在调用 cursor.callproc 后获取存储过程的结果,主要取决于以下几个因素:

  • 调用过程的结果是否被分配给 INOUT 或 OUT 参数
  • 结果是单行数据还是多个结果集
  • 使用的 Python 包是什么

DBAPI 的 规范cursor.callproc 的说明如下:

调用一个指定名称的存储数据库过程。参数序列必须包含每个过程所需的参数。调用的结果会作为输入序列的修改副本返回。输入参数保持不变,输出和输入/输出参数会被可能的新值替换。

该过程也可能提供一个结果集作为输出。这需要通过标准的 .fetch*() 方法来获取。

在实际操作中,使用 cursor.callproc 的返回值只在过程返回单行数据时有效,并且列数要与 INOUT 和 OUT 参数的数量匹配,因此处理结果的方式会有所不同。


以下是主要的 MySQL Python 连接器包如何处理这些情况的 - MySQL Connectormysqlclient (MySQLdb)PyMySQL

单行结果,通过 INOUT 或 OUT 参数返回

  • MySQL Connector 返回输入序列的修改副本作为 cursor.callproc 的返回值;这个值是一个元组。

    params = [in_param, out_param1, out_param2]
    in_, out1, out2 = cursor.callproc("test_proc", params) 
    
  • mysqlclientPyMySQL 需要查询数据库以获取输出参数,然后通过游标获取结果;这个值是一个元组的元组。要查询的参数名称格式为 '@_{procedure_name}_{params.index(param)}'

    cursor.callproc("test_proc", params)
    cursor.execute("""SELECT @_test_proc_0, @_test_proc_1""")
    result = cursor.fetchall()
    

一个或多个行在一个结果集中,没有定义 INOUT 或 OUT 参数

  • MySQL Connector 通过游标的 stored_results 方法暴露结果(cursor.stored_results 不是 DBAPI 规范的一部分)

    cursor.callproc("test_proc", params)
    results = [r.fetchall() for r in cursor.stored_results()]
    
  • mysqlclientPyMySQL 通过游标的 fetch* 方法暴露结果

    cursor.callproc("test_proc", params)
    results = cursor.fetchall()
    

多个结果集,没有定义 INOUT 或 OUT 参数

  • MySQL Connector 通过游标的 stored_results 方法暴露结果

    cursor.callproc("test_proc", params)
    results = [r.fetchall() for r in cursor.stored_results()]
    
  • mysqlclientPyMySQL 要求每个结果集都通过游标获取,同时调用 cursor.nextset 以进入下一个结果集。请注意,可能会返回一个额外的空结果集,这是调用过程的结果(如果通过 cursor.nextset 获取结果集,而不是仅调用 cursor.fetchall 一次,也会发生这种情况)。

    cursor.callproc("test_proc", params)
    results = [cursor.fetchall()]
    while cursor.nextset():
        results.append(cursor.fetchall())
    

版本信息

$ mysql --version
mysql  Ver 15.1 Distrib 10.1.41-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2

$ pip list | grep -i mysql
mysql-connector-python 8.0.18 
mysqlclient            1.4.6  
PyMySQL                0.9.3  
19

你有没有试过选择其中一个结果集呢?

for result in cursor.stored_results():
    people = result.fetchall()

可能是因为它在为多个结果集分配空间,尽管你只有一个 SELECT 语句。我知道在 PHP 的 MySQLi 中,存储过程会这样做,以便支持 INOUT 和 OUT 变量的返回(虽然你这里没有这些变量,但也许它还是在分配空间)。

我正在使用的完整代码(它是有效的)是:

import mysql.connector

cnx = mysql.connector.connect(user='me',password='pw',host='localhost',database='mydb')
cnx._open_connection()
cursor = cnx.cursor()

cursor.callproc("getperson",[1])

for result in cursor.stored_results():
    people=result.fetchall()

for person in people:
    print person

cnx.close()

撰写回答