2014-09-10 32 views
5

当生成大型结果集时,典型的MySQLdb库查询可以使用大量内存并且在Python中执行效果很差。例如:将python MySQLDB SScursor与嵌套查询结合使用

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不与嵌套查询兼容。真的吗?如果这样太糟糕了,因为主循环似乎与标准光标运行速度太慢。

回答

6

这个问题在MySQLdb的用户指南中商量了一下,the threadsafety attribute(重点煤矿)的标题下:

The MySQL protocol can not handle multiple threads using the same connection at once. Some earlier versions of MySQLdb utilized locking to achieve a threadsafety of 2. While this is not terribly hard to accomplish using the standard Cursor class (which uses mysql_store_result()), it is complicated by SSCursor (which uses mysql_use_result() ; with the latter you must ensure all the rows have been read before another query can be executed.

为MySLQ C API函数mysql_use_result()文档提供有关您的错误消息的详细信息:

When using mysql_use_result() , you must execute mysql_fetch_row() until a NULL value is returned, otherwise, the unfetched rows are returned as part of the result set for your next query. The C API gives the error Commands out of sync; you can't run this command now if you forget to do this!

换句话说,你必须完全取任何缓冲的游标结果集(即一个使用mysql_use_result()代替mysql_store_result() - 与MySQLdb的影响,这就意味着s 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的直接迭代的光标,而不是调用fetchone()明确了xrange(cursor.rowcount)。这在使用非缓冲光标时尤其重要,因为rowcount属性未定义,并且会给出非常意外的结果(请参阅:Python MysqlDB using cursor.rowcount with SSDictCursor returning wrong count)。

+0

蟒蛇 - 如此正确,如此缓慢在同一时间! – shigeta 2014-09-12 11:33:54