【问题标题】:Using the python MySQLDB SScursor with nested queries使用带有嵌套查询的 python MySQLDB SScursor
【发布时间】:2014-11-04 11:47:03
【问题描述】:

当生成大量结果集时,典型的 MySQLdb 库查询可能会使用大量内存并且在 Python 中表现不佳。例如:

cursor.execute("SELECT id, name FROM `table`")
for i in xrange(cursor.rowcount):
    id, name = cursor.fetchone()
    print id, name

有一个可选的游标一次只获取一行,确实加快了脚本的速度并大大减少了脚本的内存占用。

import MySQLdb
import MySQLdb.cursors

conn = MySQLdb.connect(user="user", passwd="password", db="dbname", 
                       cursorclass = MySQLdb.cursors.SSCursor)
cur = conn.cursor()
cur.execute("SELECT id, name FROM users")
row = cur.fetchone()
while row is not None:
    doSomething()
    row = cur.fetchone()    
cur.close()
conn.close()

但我找不到任何关于将SSCursor 与嵌套查询一起使用的信息。如果这是doSomething()的定义:

def doSomething()
    cur2 = conn.cursor()
    cur2.execute('select id,x,y from table2')
    rows = cur2.fetchall()
    for row in rows:
        doSomethingElse(row)
    cur2.close()

然后脚本抛出以下错误:

_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now")

听起来好像SSCursor 与嵌套查询不兼容。真的吗?如果是这样,那就太糟糕了,因为使用标准光标时主循环似乎运行得太慢了。

【问题讨论】:

    标签: python mysql-python


    【解决方案1】:

    这个问题在 MySQLdb 用户指南的 the threadsafety attribute(强调我的)标题下讨论了一点:

    MySQL 协议不能处理使用相同的多个线程 一次连接。一些早期版本的 MySQLdb 使用了锁定 实现 2 的线程安全性。虽然这并不难 使用标准 Cursor 类(它使用 mysql_store_result()),它被 SSCursor 复杂化了(它使用 mysql_use_result(); 对于后者,您必须确保所有行都有 在执行另一个查询之前已被读取。

    MySQL C API 函数mysql_use_result() 的文档提供了有关您的错误消息的更多信息:

    使用mysql_use_result()时,必须执行mysql_fetch_row() 直到返回 NULL 值,否则,未提取的行是 作为下一个查询的结果集的一部分返回。 C API 给出错误Commands out of sync; you can't run this command now 如果你忘了这样做!

    换句话说,您必须从任何无缓冲游标(即使用mysql_use_result() 而不是mysql_store_result() - 与MySQLdb,这意味着SSCursorSSDictCursor)完全获取结果集,然后才能执行同一连接上的另一个语句。

    在您的情况下,最直接的解决方案是打开第二个连接以在迭代无缓冲查询的结果集时使用。 (简单地从同一个连接中获取缓冲游标是行不通的;在使用缓冲游标之前,您仍然必须超越未缓冲的结果集。)

    如果您的工作流程类似于“循环遍历一个大结果集,为每一行执行 N 个小查询”,请考虑查看 MySQL 的存储过程,作为嵌套来自不同连接的游标的替代方法。您仍然可以使用 MySQLdb 调用过程并获取结果,但您肯定会想要 read the documentation of MySQLdb's callproc() method,因为它在检索过程输出时不符合 Python 的 database API specs


    第二种选择是坚持使用缓冲游标,但将您的查询分成多个批次。这就是我去年为一个项目所做的,我需要遍历一组数百万行,使用内部模块解析一些数据,并在处理每个查询后执行一些 INSERTUPDATE 查询排。总体思路是这样的:

    QUERY = r"SELECT id, name FROM `table` WHERE id BETWEEN %s and %s;"
    BATCH_SIZE = 5000
    
    i = 0
    while True:
        cursor.execute(QUERY, (i + 1, i + BATCH_SIZE))
        result = cursor.fetchall()
    
        # If there's no possibility of a gap as large as BATCH_SIZE in your table ids,
        # you can test to break out of the loop like this (otherwise, adjust accordingly):
        if not result:
            break
    
        for row in result:
            doSomething()
    
        i += BATCH_SIZE
    

    关于您的示例代码,我要注意的另一件事是,您可以直接在 MySQLdb 中的游标上进行迭代,而不是在 xrange(cursor.rowcount) 上显式调用 fetchone()。这在使用无缓冲游标时尤其重要,因为 rowcount 属性未定义,会产生非常意外的结果(请参阅:Python MysqlDB using cursor.rowcount with SSDictCursor returning wrong count)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-20
      • 2015-10-11
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 2017-02-12
      • 1970-01-01
      • 2015-11-26
      相关资源
      最近更新 更多