显式调用cursor.close()的必要性

50 投票
5 回答
30739 浏览
提问于 2025-04-18 12:41

有时候,我会直接用 connection.cursor() 来执行原始查询,而不是使用ORM(因为ORM并不是万能的解决方案)。

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

据我了解,Django中的 connectioncursor 遵循“Python数据库API规范v2.0”(PEP-249)。根据这个规范,cursor 会在调用 __del__() 方法时自动关闭。我想问的也可以是:有没有可能这个方法不被调用的情况?

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

5 个回答

1

我对这个问题的回答有点晚了。也许你想要的是一种在退出时关闭的作用域。

from contextlib import closing
from django.db import connection

with closing(connection.cursor()) as cursor:
    cursor.execute(...)
    cursor.execute(...)
    cursor.execute(...)
2

虽然操作系统通常会自动释放资源,但我们还是应该养成良好的习惯,主动关闭一些东西,比如数据库连接,这样可以确保在不需要的时候资源能够被释放。从数据库的角度来看,最重要的是要确保所有的更改都被 commit() 了。

2

明确调用 cursor.close() 可能有两个原因:

  1. __del__ 这个方法不一定会被调用,而且它有一些问题,你可以在 这里这里 阅读更多信息。
  2. 明确的做法总是比含糊的做法要好(这也是 Python的哲学之一)。
26

Django中的cursor类其实就是对底层数据库cursor的一个封装,所以如果不关闭cursor,具体的影响就要看底层数据库驱动的表现了。

根据psycopg2的常见问题解答(psycopg2是Django用来连接PostgreSQL数据库的驱动),它们的游标是比较轻量的,但会缓存你通过这个游标查询到的数据,这可能会浪费内存:

游标是轻量级的对象,创建很多游标通常不会有问题。但要注意,用于获取结果集的游标会缓存数据,并且使用的内存量与结果集的大小成正比。我们的建议是,几乎总是创建一个新的游标,并在不再需要数据时尽快关闭旧的游标(调用close()方法)。唯一的例外是一些紧密循环的情况,通常会在一堆INSERT或UPDATE操作中使用同一个游标。

Django使用MySQLdb作为MySQL的后端,这里有几种不同类型的游标,其中一些实际上会将结果集存储在服务器端。MySQLdb文档中关于Cursor.close特别提到,完成后关闭服务器端游标是非常重要的:

如果你使用的是服务器端游标,完成后一定要关闭游标,并在创建新游标之前这样做。

不过,这对Django来说并不相关,因为它使用的是MySQLdb提供的默认Cursor类,这种游标是将结果存储在客户端的。留下一个已使用的游标不关闭,只会浪费存储结果集所占用的内存,就像psycopg2一样。游标上的close方法只是删除了对数据库连接的内部引用,并释放了存储的结果集:

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_oraclesqlite3/pysqlite2)都遵循相同的模式;通过删除或重置存储的结果/对象引用来释放内存。sqlite3文档甚至没有提到Cursor一个close方法,而这个方法在示例代码中仅偶尔使用。

你说得对,当对cursor对象调用__del__()时,cursor会被关闭,所以只有在你长时间保留对cursor的引用时,才需要显式关闭;比如说,作为类的实例方法保留的self.cursor对象。

13

__del__/.close():

  1. __del__这个方法不一定会被调用。
  2. 有些数据库在它们的__del__里不调用cursor.close()(虽然这是不好的做法,但确实存在)。
  3. 有些数据库在连接函数里实际上并不创建连接,而是在游标函数里创建(比如pyhive的presto,可能他们后来修复了这个问题)。

关于服务器连接的一般情况

大多数服务器都有一个空闲超时的设置(我们称之为T)。如果一个连接在T秒内没有活动,服务器就会把这个连接关闭。大多数服务器还可以设置工作线程池的大小(我们称之为W)。如果你已经有W个连接到服务器,当你尝试新连接时,可能会出现卡顿。想象一下,如果你没有办法手动关闭连接,在这种情况下,你需要把超时时间设置得足够小,这样你的工作池就不会被完全占满,这取决于你有多少个同时连接。

但是,如果你确实关闭了游标/连接(即使在上面提到的[3]中不完全相同,它们的行为也类似),那么你就不需要管理这些服务器的配置属性,你的线程池只需要足够大以处理所有的并发连接(偶尔可以等待新资源的到来)。我见过一些服务器(比如Cassandra上的Titan)在工作线程池用完后无法恢复,整个服务器会一直宕机,直到重启。

总结

如果你使用的是非常成熟的库,比如dano提到的那些,你就不会遇到问题。如果你使用的是一些不太完善的库,可能会因为不调用.close()而在服务器获取工作线程时出现阻塞,这取决于你的服务器配置和访问频率。

撰写回答