如何在Python中获取逐行MySQL结果集
在完成任何工作之前,MySQL ResultSets默认从服务器完全检索。 在巨大的结果集的情况下,这变得不可用。 我希望实际上从服务器中逐一检索行。
在Java中,按照这里的说明(在“ResultSet”下),我创build了一个像这样的语句:
stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE);
这在Java中很好地工作。 我的问题是:是否有办法在Python中做同样的事情?
我试过的一件事是限制查询一次1000行,如下所示:
start_row = 0 while True: cursor = conn.cursor() cursor.execute("SELECT item FROM items LIMIT %d,1000" % start_row) rows = cursor.fetchall() if not rows: break start_row += 1000 # Do something with rows...
但是,这似乎越慢,start_row越高。
不,使用fetchone()
而不是fetchall()
不会改变任何东西。
澄清:
我用来重现这个问题的天真的代码如下所示:
import MySQLdb conn = MySQLdb.connect(user="user", passwd="password", db="mydb") cur = conn.cursor() print "Executing query" cur.execute("SELECT * FROM bigtable"); print "Starting loop" row = cur.fetchone() while row is not None: print ", ".join([str(c) for c in row]) row = cur.fetchone() cur.close() conn.close()
在〜700,000行的表上,这段代码运行得很快。 但是在一个约9,000,000行的表上,它打印出“执行查询”,然后挂起很长时间。 这就是为什么我使用fetchone()
或fetchall()
没有什么区别。
我认为你必须连接传递cursorclass = MySQLdb.cursors.SSCursor
:
MySQLdb.connect(user="user", passwd="password", db="mydb", cursorclass = MySQLdb.cursors.SSCursor )
即使不使用fetchall
,默认的光标也会一次获取所有数据。
编辑: SSCursor
或任何其他支持服务器端结果集的游标类 – 检查MySQLdb.cursors
上的模块文档。
限制/偏移解决scheme运行在二次时间,因为mysql必须重新扫描行以find偏移量。 如您所怀疑的那样,默认的游标会将整个结果集存储在客户端上,这会消耗大量的内存。
相反,您可以使用服务器端游标,该游标会保持查询运行并根据需要提取结果。 游标类可以通过给连接调用本身提供一个默认值,或者每次为游标方法提供一个类来定制。
from MySQLdb import cursors cursor = conn.cursor(cursors.SSCursor)
但这不是全部。 除了存储mysql结果之外,默认的客户端游标实际上每行都取一个。 这种行为是无证的,非常不幸的。 这意味着为所有行创build完整的python对象,这会比原始的mysql结果消耗更多的内存。
在大多数情况下,存储在作为迭代器打包的客户端上的结果将产生合理的内存使用情况下的最佳速度。 但是如果你想要的话,你必须自己推出。
你试过这个版本的fetchone吗? 或者不同的东西?
row = cursor.fetchone() while row is not None: # process row = cursor.fetchone()
还有,你试过这个吗?
row = cursor.fetchmany(size=1) while row is not None: # process row = cursor.fetchmany( size=1 )
并不是所有的驱动程序都支持这些,所以你可能得到错误或者发现它们太慢了。
编辑。
当它挂在执行上,你正在等待数据库。 这不是一行一行的Python的东西; 这是一个MySQL的事情。
MySQL更喜欢把所有的行作为自己的cachingpipe理的一部分。 这是通过提供Integer.MIN_VALUE(-2147483648L)的fetch_size来closures的。
问题是,Python DBAPI的哪一部分变成了JDBC fetch_size的等价物?
我想这可能是游标的arraysize属性。 尝试
cursor.arraysize=-2**31
看看是否强制MySQLstream式传输结果集,而不是caching它。
尝试使用MySQLdb.cursors.SSDictCursor
con = MySQLdb.connect(host=host, user=user, passwd=pwd, charset=charset, port=port, cursorclass=MySQLdb.cursors.SSDictCursor); cur = con.cursor() cur.execute("select f1, f2 from table") for row in cur: print row['f1'], row['f2']
我发现最好的结果混合了一些其他的答案。
这包括将cursorclass=MySQLdb.cursors.SSDictCursor
(用于MySQLdb)或pymysql.cursors.SSDictCursor
(用于PyMySQL)作为连接设置的一部分。 这将让服务器保持查询/结果(“SS”代表服务器端,而不是默认的游标,带来的结果客户端),并build立一个字典出每行(例如{'id':1,名字':'Cookie Monster'})。
然后循环遍历行,Python 2.7和3.4中有一个无限循环, while rows is not None
因为即使cur.fetchmany(size=10000)
被调用,也没有结果,方法返回一个空列表( []
)而不是无。
实际例子:
query = """SELECT * FROM my_table""" conn = pymysql.connect(host=MYSQL_CREDENTIALS['host'], user=MYSQL_CREDENTIALS['user'], passwd=MYSQL_CREDENTIALS['passwd'], charset='utf8', cursorclass = pymysql.cursors.SSDictCursor) cur = conn.cursor() results = cur.execute(query) rows = cur.fetchmany(size=100) while rows: for row in rows: process(row) rows = cur.fetchmany(size=100) cur.close() conn.close()