大查询后psycopg2泄漏内存

时间:2013-06-19 18:55:56

标签: python postgresql psycopg2

我正在使用psycopg2(我升级到2.5版)在我的postgres数据库的python脚本中运行一个大型查询。查询完成后,我关闭光标和连接,甚至运行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()

3 个答案:

答案 0 :(得分:34)

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

而不是

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计数2000 FROM my_cursor_name"但事实证明,如果你只是覆盖" name = None"那么psycopg会为你做这件事。创建游标时的默认参数。

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

答案 1 :(得分:9)

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


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

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

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

如果程序可以压缩通过内存散布的剩余对象,则只能将内存释放到操作系统。这只有在使用间接句柄引用时才有可能,因为否则移动对象会使对象的现有指针无效。间接引用效率相当低,特别是在现代CPU中,追逐指针会对性能产生可怕的影响。

除非程序特别谨慎,否则通常会发生的事情是,brk()所分配的每一大块内存都会出现,但仍会使用一些小块。

操作系统无法判断程序是否认为此内存仍在使用中,因此它无法将其恢复原状。由于程序不倾向于访问内存,因此OS通常会随着时间的推移将其交换掉,从而释放物理内存用于其他用途。这是你应该有交换空间的原因之一。

可以编写将内存交回操作系统的程序,但我不确定你是否可以用Python来实现。

另见:

所以:这实际上不是内存泄漏。如果你做了一些使用大量内存的事情,那么这个过程不应该增长太多,如果有的话,它会重新使用上一个大分配中以前释放的内存。

答案 2 :(得分:7)

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()

正如您将看到的那样,点组来得很快,而不是暂停以获得行缓冲区(itersize),因此您无需使用fetchmany来提高性能。当我使用/usr/bin/time -v运行时,我在不到3分钟内得到结果,仅使用200MB的RAM(而不是带有客户端游标的60GB)用于1000万行。服务器不需要更多ram,因为它使用临时表。