26

我正在使用 psycopg2(我升级到版本 2.5)在 python 脚本中针对我的 postgres 数据库运行大型查询。查询完成后,我关闭游标和连接,甚至运行gc,但该过程仍然消耗大量内存(确切地说是7.3gb)。我错过了清理步骤吗?

import psycopg2
conn = psycopg2.connect("dbname='dbname' user='user' host='host'")
cursor = conn.cursor()
cursor.execute("""large query""")
rows = cursor.fetchall()
del rows
cursor.close()
conn.close()
import gc
gc.collect()
4

3 回答 3

57

我遇到了类似的问题,经过几个小时的血汗和泪水,发现答案只需要添加一个参数。

代替

cursor = conn.cursor()

cursor = conn.cursor(name="my_cursor_name")

或者更简单

cursor = conn.cursor("my_cursor_name")

详细信息可在http://initd.org/psycopg/docs/usage.html#server-side-cursors找到

我发现这些说明有点令人困惑,因为我需要重写我的 SQL 以包括“DECLARE my_cursor_name ....”,然后是“FETCH count 2000 FROM my_cursor_name”,但事实证明 psycopg 为你做了这一切如果您在创建游标时简单地覆盖“name=None”默认参数,那么引擎盖。

上面使用 fetchone 或 fetchmany 的建议并不能解决问题,因为如果您未设置 name 参数,psycopg 默认会尝试将整个查询加载到 ram 中。您可能需要做的唯一另一件事(除了声明名称参数)是如果您的内存仍然太少,请将 cursor.itersize 属性从默认的 2000 更改为 1000。

于 2015-01-14T10:08:11.970 回答
11

请参阅@joeblog的下一个答案以获得更好的解决方案。


首先,你不应该首先需要所有的 RAM。您应该在这里做的是获取结果集的。不要做一个fetchall(). 相反,使用更有效的cursor.fetchmany方法。请参阅psycopg2 文档

现在,解释为什么它没有被释放,以及为什么这不是正式正确使用该术语的内存泄漏。

大多数进程在释放内存时不会将内存释放回操作系统,它们只是使其可用于在程序的其他地方重复使用。

只有当程序可以压缩分散在内存中的剩余对象时,内存才能释放给操作系统。这仅在使用间接句柄引用时才有可能,因为否则移动对象会使指向该对象的现有指针无效。间接引用的效率相当低,尤其是在现代 CPU 上,追逐指针会对性能造成可怕的影响。

除非程序格外小心,否则通常会发生的情况是,分配给每个大块内存的brk()土地上都有一些仍在使用的小块。

操作系统无法判断程序是否认为该内存仍在使用中,因此它不能直接将其收回。由于程序不倾向于访问内存,因此操作系统通常会随着时间的推移将其换出,从而释放物理内存以供其他用途。这是您应该拥有交换空间的原因之一。

可以编写将内存交还给操作系统的程序,但我不确定您是否可以使用 Python 来做到这一点。

也可以看看:

所以:这实际上不是内存泄漏。如果您执行其他使用大量内存的操作,则该进程根本不会增长太多,它将重新使用上次大分配中先前释放的内存。

于 2013-06-20T01:20:53.313 回答
11

Joeblog 有正确的答案。处理获取的方式很重要,但比必须定义游标的方式要明显得多。这是一个简单的示例来说明这一点,并为您提供一些可以复制粘贴的内容。

import datetime as dt
import psycopg2
import sys
import time

conPG = psycopg2.connect("dbname='myDearDB'")
curPG = conPG.cursor('testCursor')
curPG.itersize = 100000 # Rows fetched at one time from the server

curPG.execute("SELECT * FROM myBigTable LIMIT 10000000")
# Warning: curPG.rowcount == -1 ALWAYS !!
cptLigne = 0
for rec in curPG:
   cptLigne += 1
   if cptLigne % 10000 == 0:
      print('.', end='')
      sys.stdout.flush() # To see the progression
conPG.commit() # Also close the cursor
conPG.close()

正如您将看到的,点按组快速出现,而不是暂停以获得行的缓冲区(迭代大小),因此您不需要使用fetchmany性能。当我使用 运行它时/usr/bin/time -v,我在不到 3 分钟的时间内就得到了结果,1000 万行只使用了 200MB 的 RAM(而不是使用客户端游标的 60GB)。服务器不需要更多内存,因为它使用临时表。

于 2015-04-16T00:59:44.117 回答