大查询后psycopg2泄漏内存

Ada*_*ine 18 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()
Run Code Online (Sandbox Code Playgroud)

joe*_*log 40

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

代替

cursor = conn.cursor()
Run Code Online (Sandbox Code Playgroud)

cursor = conn.cursor(name="my_cursor_name")
Run Code Online (Sandbox Code Playgroud)

或者更简单

cursor = conn.cursor("my_cursor_name")
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请访问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参数unset,psycopg默认会尝试将整个查询加载到ram中.您可能需要做的唯一其他事情(除了声明一个名称参数)是将cursor.itersize属性从默认的2000更改为1000,如果您仍然有太少的内存.

  • @RyanTuck 似乎您可以通过将 `server_sider_cursors=True` 传递给 `create_engine` 在 `sqlalchemy` 中完成此操作:http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=server_side_cursors#psycopg2 - 连接参数 (2认同)
  • @FoxMulder900 根据记录,看起来 `server_side_cursors` 已被 `stream_results` 替换 (2认同)

Cra*_*ger 9

请查看@joeblog 的下一个答案,以获得更好的解决方案.


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

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

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

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

通常会发生什么事情,除非程序提供额外的谨慎,每个大块的内存分配着brk()一些小块仍然在使用.

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

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

也可以看看:

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

  • 虽然这里说的一切都是正确的,但查询结果通常会完全在客户端传输(不是通过`fetch*()`而是通过`execute()`.因此,使用`fetchmany()`而不是`fetchall()`可以在Python对象创建方面节省一些内存,使用@joeblog建议的服务器端游标是正确的解决方案. (3认同)

Le *_*oid 8

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()
Run Code Online (Sandbox Code Playgroud)

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