显式cursor.close()的必要性

42 人关注

不时地,我在执行原始查询时使用 connection.cursor() 而不是使用ORM(因为它绝对不是一个银弹)。

我注意到,在几个地方,我在完成数据库后没有明确调用 cursor.close() 。到目前为止,这并没有导致任何错误,或者性能问题。我在想,如果不明确关闭游标,我可能会有什么样的问题,会出什么问题?

据我所知,Django中的 connection cursor 遵循《Python数据库API规范v2.0》( PEP-249 ).而且,根据它,只要 cursor 方法被调用, __del__() 就会自动关闭。我想问题也可以是这样的。是否存在不调用该方法的情况?

顺便说一下,我使用的是Python 2.7和Django 1.6.5。

2 个评论
我想这与这个问题非常相似 stackoverflow.com/questions/5669878/... ??
@AamirAdnan 谢谢,不完全是--在发帖前看到的。我知道如何关闭光标。问题是,不关闭它有多糟,后果是什么。
python
django
database-connection
database-cursor
alecxe
alecxe
发布于 2014-07-10
5 个回答
dano
dano
发布于 2015-10-28
已采纳
0 人赞同

Django的 cursor 类只是对底层DB的 cursor 的一个封装,所以打开 cursor 的效果基本上是与底层DB驱动绑定的。

根据psycopg2的(psycopg2是Django用于PostgreSQL数据库的DB驱动)。 常见问题 ,他们的游标是轻量级的,但会缓存你使用游标对象进行查询时返回的数据,这有可能会浪费内存。

游标是轻量级的对象,创建大量的游标应该不会造成任何问题。 构成任何类型的问题。但是请注意,用于获取结果的游标 的游标会缓存数据,并按照结果集大小的比例使用内存。 集的大小。我们的建议是,几乎总是创建一个新的游标,并在 一旦不再需要这些数据,就立即处理旧的游标(调用 close())。唯一的例外是紧密循环,在这种循环中,我们通常 使用同一个游标进行大量的INSERTs或UPDATEs。

Django使用 MySQLdb 作为MySQL的后端,它有几种不同类型的游标,包括一些实际上在服务器端存储其结果集的游标。替换代码 MySQLdb documentation for Cursor.close 请注意,当你使用完服务器端的游标时,关闭它们是非常重要的。

如果你使用的是服务器端游标,那么在使用完游标并创建新的游标之前,关闭游标是非常重要的。 是非常重要的,当你用完游标后,在创建一个新的游标之前,必须关闭该游标。

然而,这与Django无关,因为它使用了由 MySQLdb 提供的默认的 Cursor 类,它在客户端存储结果。让一个使用过的游标处于开放状态,就像 psycopg2 一样,有可能浪费存储的结果集所使用的内存。替换代码 close method 在游标上只是删除了对数据库连接的内部引用,并耗尽了存储的结果集。

def close(self):
    """Close the cursor. No further queries will be possible."""
    if not self.connection: return
    while self.nextset(): pass
    self.connection = None

就我看他们的源代码而言,Django使用的其余所有后端(cx_oracle, sqlite3/pysqlite2)都遵循同样的模式;通过删除/重设存储的结果/对象引用来释放内存。这就是所谓的sqlite3 docs甚至不提Cursor一个关闭方法,而且它只是在所附的示例代码中零星地使用。

你是对的,当在cursor对象上调用__del__()时,一个cursor将被关闭,所以只有当你对cursor保持长期引用时才需要显式关闭。e.例如,一个self.cursor对象,你把它作为一个类的实例方法来保存。

然而,如果你在第二次查询中重新使用游标,那么它在第一次查询中缓存的数据将不再被引用。所以从某种意义上说,不必要地创建一大堆游标可能会起到反作用,除非你特别删除已使用的游标。
dano
@holdenweb 对,如果你要在短时间内进行大量的查询,使用同一个游标可能是有意义的。如果你打算做一个查询,然后在未来的某个未知点做另一个查询,你可能应该关闭/删除游标,这样你就不会保留它用来缓存结果集的内存。
看来我们有激烈的协议。
最近django(1.5+)在处理事务方面的变化值得注意,特别是如果你经常使用原始查询。 请查看最新的开发文档。 docs.djangoproject.com/en/dev/topics/db/transactions
替换代码0】将确保游标按预期关闭,同时,代码仍然看起来很紧凑。
metaperture
metaperture
发布于 2015-10-28
0 人赞同

__del__ / .close() :

  • __del__ is not guaranteed to be called
  • some databases don't call cursor.close() in their __del__ (bad practice, but true)
  • some databases don't actually create connections in the connection function, but in the cursor function instead (e.g. for 2&3: pyhive's presto [maybe they've since patched it])
  • 关于一般的服务器连接

    大多数服务器都有一个空闲超时的配置属性(我们称之为T)。如果一个连接的空闲时间超过了T秒,服务器就会删除该连接。大多数服务器也有设置工作者线程池(W)大小的属性。如果你的服务器已经有了W个连接,那么当试图建立一个新的连接时,它很可能会挂起。想象一下,你没有显式关闭连接的选项。在这种情况下,你必须把超时设置得足够小,以使你的工人池永远不会被完全使用,这是一个关于你有多少并发连接的函数。

    然而,如果你确实关闭了你的游标/连接(即使不等同于上述[3],它们的行为也是类似的),那么你就不必管理这些服务器配置属性,你的线程池只需要足够大,以管理所有并发连接(可以选择偶尔等待新资源)。我见过一些服务器(例如Cassandra上的Titan)无法从线程池中的工作者耗尽中恢复过来,所以整个服务器会宕机,直到重新启动。

    TL/DR 如果你使用的是非常完善的库,比如 dano 提到的那些,你就不会有问题。如果你使用的是不太纯净的库,如果你不调用 .close() ,你可能会在服务器上获得一个工作线程而导致阻塞,这取决于你的服务器配置和访问率。

    holdenweb
    holdenweb
    发布于 2015-10-28
    0 人赞同

    虽然通常可以依靠操作系统来释放资源,但关闭数据库连接等事情总是很卫生,以确保资源在不再需要时被释放,从数据库的角度来看,真正重要的事情是确保任何变化都是 commit() 的。

    Aamir Rind
    Aamir Rind
    发布于 2015-10-28
    0 人赞同

    明确调用 cursor.close() 可能是因为两个原因。

  • __del__ is not guaranteed to be called and has some issues you can read here and here
  • Explicit is better than implicit ( Zen of Python )
  • stanleyxu2005
    stanleyxu2005
    发布于 2015-10-28
    0 人赞同

    我对这个问题提得有点晚。也许你想要的是一个接近出口的范围。

    from contextlib import closing
    from django.db import connection