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