如何在python中获取逐行的MySQL ResultSet

时间:2008-12-03 15:23:49

标签: python mysql

默认情况下,MySQL ResultSet会在完成任何工作之前从服务器中完全检索。在巨大的结果集的情况下,这变得无法使用。我希望实际上从服务器中逐个检索行。

在Java中,按照here的说明(在“ResultSet”下),我创建了一个这样的语句:

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()时没有任何区别。

5 个答案:

答案 0 :(得分:50)

我认为你必须连接传递cursorclass = MySQLdb.cursors.SSCursor

 MySQLdb.connect(user="user", 
                 passwd="password",
                 db="mydb",
                 cursorclass = MySQLdb.cursors.SSCursor
                )

即使您不使用fetchall,默认光标也会立即获取所有数据。

编辑:SSCursor或支持服务器端结果集的任何其他游标类 - 检查MySQLdb.cursors上的模块文档。

答案 1 :(得分:17)

限制/偏移解决方案以二次方运行,因为mysql必须重新扫描行才能找到偏移量。如您所料,默认光标将整个结果集存储在客户端上,这可能会占用大量内存。

相反,您可以使用服务器端游标,它可以使查询保持运行并根据需要获取结果。可以通过向连接调用本身提供默认值,或者每次向游标方法提供一个类来自定义游标类。

from MySQLdb import cursors
cursor = conn.cursor(cursors.SSCursor)

但这不是整个故事。除了存储mysql结果之外,默认的客户端游标实际上无论如何都会获取每一行。这种行为没有记录,非常不幸。这意味着为所有行创建了完整的python对象,这比原始的mysql结果消耗的内存要多得多。

在大多数情况下,存储在客户端上的结果作为迭代器包装将产生具有合理内存使用的最佳速度。但如果你愿意的话,你必须自己动手。

答案 2 :(得分:7)

你试过这个版本的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更喜欢将所有行作为其自身缓存管理的一部分来获取。通过提供Integer.MIN_VALUE(-2147483648L)的fetch_size来关闭它。

问题是,Python DBAPI的哪一部分相当于JDBC fetch_size?

我认为它可能是游标的arraysize属性。尝试

cursor.arraysize=-2**31

看看是否强制MySQL流式传输结果集而不是缓存它。

答案 3 :(得分:2)

我发现最好的结果与其他一些答案混合了一点。

这包括设置cursorclass=MySQLdb.cursors.SSDictCursor(对于MySQLdb)或pymysql.cursors.SSDictCursor(对于PyMySQL)作为连接设置的一部分。这将让服务器保持查询/结果(“SS”代表服务器端而不是默认光标,它带来结果客户端)并在每行中构建一个字典(例如{'id':1,' name':'Cookie Monster'})。

然后循环遍历行,由while rows is not None引起的Python 2.7和3.4都有一个无限循环,因为即使调用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()

答案 4 :(得分:1)

尝试使用 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']