28

我在 python 中使用psycopg2模块从 postgres 数据库中读取,我需要对列中的所有行进行一些操作,该列有超过 100 万行。

我想知道会cur.fetchall()失败还是导致我的服务器宕机?(因为我的 RAM 可能没有那么大来保存所有数据)

q="SELECT names from myTable;"
cur.execute(q)
rows=cur.fetchall()
for row in rows:
    doSomething(row)

更聪明的方法是什么?

4

6 回答 6

42

Burhan 指出的解决方案通过仅获取单行来减少大型数据集的内存使用:

行 = cursor.fetchone()

但是,我注意到逐一获取行的速度显着下降。我通过 Internet 连接访问外部数据库,这可能是它的一个原因。

事实证明,拥有服务器端游标并获取多行行是最高效的解决方案。您可以更改 sql 语句(如 alecxe 的答案),但也有使用 psycopg2 提供的功能的纯 python 方法:

cursor = conn.cursor('name_of_the_new_server_side_cursor')
cursor.execute(""" SELECT * FROM table LIMIT 1000000 """)

while True:
    rows = cursor.fetchmany(5000)
    if not rows:
        break

    for row in rows:
        # do something with row
        pass

您可以在psycopg2 wiki中找到有关服务器端光标的更多信息

于 2013-11-08T15:11:48.893 回答
30

考虑使用服务器端游标

当执行数据库查询时,Psycopg 游标通常会获取后端返回的所有记录,并将它们传输到客户端进程。如果查询返回的数据量很大,客户端将按比例分配大量内存。

如果数据集太大而无法在客户端实际处理,则可以创建服务器端游标。使用这种游标,可以只向客户端传输受控数量的数据,这样就可以检查大型数据集,而无需将其完全保存在内存中。

这是一个例子:

cursor.execute("DECLARE super_cursor BINARY CURSOR FOR SELECT names FROM myTable")
while True:
    cursor.execute("FETCH 1000 FROM super_cursor")
    rows = cursor.fetchall()

    if not rows:
        break

    for row in rows:
        doSomething(row)
于 2013-07-29T20:17:39.513 回答
27

fetchall()获取到arraysize限制,因此为了防止对您的数据库造成大量打击,您可以以可管理的批次获取行,或者简单地遍历游标直到其耗尽:

row = cur.fetchone()
while row:
   # do something with row
   row = cur.fetchone()
于 2013-07-29T20:20:09.727 回答
3

这是用于具有管理速度的简单服务器端光标fetchmany的代码。

原则是在 Psycopg2中使用命名游标itersize,并像这样做一样一次加载许多行,fetchmany但使用单个循环for rec in cursor执行隐式fetchnone().

使用此代码,我在 1 小时内从数十亿行表中查询了 1.5 亿行和 200 兆内存。

于 2015-04-17T19:28:21.867 回答
1

编辑:使用 fetchmany(连同 fetchone() 和 fetchall(),即使有行限制(arraysize)仍将发送整个结果集,将其保留在客户端(存储在底层 c 库中,我认为是 libpq)用于任何其他fetchmany() 调用等。如果不使用命名游标(这将需要打开事务),您必须在 sql 中使用 limit 和 order-by,然后分析结果并使用以下命令扩充下一个查询where (ordered_val = %(last_seen_val)s and primary_key > %(last_seen_pk)s OR ordered_val > %(last_seen_val)s)

至少可以说,这对图书馆来说是一种误导,并且文档中应该对此有一个简介。我不知道为什么它不在那里。


不确定命名光标是否适合而不需要交互地向前/向后滚动?我在这里可能是错的。

循环很乏味,fetchmany但我认为这是最好的解决方案。为了让生活更轻松,您可以使用以下内容:

from functools import partial
from itertools import chain

# from_iterable added >= python 2.7
from_iterable = chain.from_iterable

# util function
def run_and_iterate(curs, sql, parms=None, chunksize=1000):
    if parms is None:
        curs.execute(sql)
    else:
        curs.execute(sql, parms)
    chunks_until_empty = iter(partial(fetchmany, chunksize), [])
    return from_iterable(chunks_until_empty)

# example scenario
for row in run_and_iterate(cur, 'select * from waffles_table where num_waffles > %s', (10,)):
    print 'lots of waffles: %s' % (row,)
于 2019-07-13T00:33:22.810 回答
0

当我阅读评论和答案时,我想我应该fetchone为未来的读者澄清一些关于服务器端游标的内容。

使用普通游标(客户端),Psycopg 获取后端返回的所有记录,并将它们传输到客户端进程。整个记录都在客户的记忆中。这是当您执行类似的查询时curs.execute('SELECT * FROM ...'

所有fetch*方法都可以访问这些存储的数据

问:那么如何fetchone帮助我们提高记忆力呢?
A:它只从存储的数据中获取一条记录,并创建一个 Python 对象并将您的 Python 代码交给您,同时将从该数据fetchall中获取并创建n 个Python 对象,并将其以一个块的形式交给您。

所以如果你的表有 1,000,000 条记录,这就是内存中发生的事情:

curs.execute --> whole 1,000,000 result set  +  fetchone --> 1 Python object
curs.execute --> whole 1,000,000 result set  +  fetchall --> 1,000,000 Python objects

当然有fetchone帮助,但我们仍然将全部记录保存在内存中。这是服务器端游标发挥作用的地方:

PostgreSQL 也有自己的游标概念(有时也称为门户)。创建数据库游标时,不一定会完全处理查询:服务器可能只能在需要时生成结果。仅将请求的结果传输给客户端:如果查询结果非常大,但客户端只需要前几条记录,则可以仅传输它们。...他们的界面是相同的,但在幕后他们发送命令来控制服务器上光标的状态(例如,当获取新记录或使用滚动()移动时)。

因此,您不会将整个结果集放在一个块中。

缺点:

缺点是服务器需要跟踪部分处理的结果,因此它在服务器上使用了更多的内存和资源。

于 2022-02-06T16:25:02.160 回答