使用带有嵌套查询的python MySQLDB SScursor

时间:2014-09-10 17:56:44

标签: python mysql-python

当生成大型结果集时,典型的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与嵌套查询不兼容。真的吗?如果是这样太糟糕了,因为主循环似乎用标准光标运行得太慢了。

1 个答案:

答案 0 :(得分:8)

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

  

MySQL协议无法使用相同的处理多个线程   立刻连接。一些早期版本的MySQLdb使用了锁定   实现2的线程安全。虽然这不是非常难   完成使用标准的Cursor类(使用   mysql_store_result()),由SSCursor(使用   mysql_use_result(); 后者必须确保所有行都有   在执行另一个查询之前已被阅读。

MySLQ 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完全获取结果集,这意味着SSCursor和{{ 1}})之前可以在同一个连接上执行另一个语句。

在您的情况下,最直接的解决方案是在迭代未缓冲查询的结果集时打开第二个连接。 (简单地从同一连接获取缓冲光标不会起作用;在使用缓冲光标之前,您仍然需要超过无缓冲的结果集。)

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


第二种方法是坚持使用缓冲光标,但将查询拆分为批量。这就是我去年为一个项目做的事情,我需要遍历一组数百万行,用内部模块解析一些数据,并执行一些SSDictCursor和{ {1}}处理每一行后的查询。一般的想法看起来像这样:

INSERT

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