如果我在Python SQLite中不关闭数据库连接会发生什么?

107

我正在做类似于这样的事情...

conn = sqlite3.connect(db_filename)

with conn:
    cur = conn.cursor()
    cur.execute( ... )

with自动提交更改。但是文档中没有提到关闭连接。

实际上,我可以在后面的语句中使用conn(我已经测试过)。因此,似乎上下文管理器不会关闭连接。

我需要手动关闭连接吗?如果保持打开状态会发生什么?

编辑

我的发现:

  • 在上下文管理器中没有关闭连接,我已经测试并确认了。在__exit__时,上下文管理器只通过执行conn.commit()来提交更改
  • with connwith sqlite3.connect(db_filename) as conn是相同的,因此使用任何一个都将保持连接处于活动状态
  • with语句不会创建新的作用域,因此在其内部声明的所有变量都可在其外部访问
  • 最后,您应该手动关闭连接

2
如果你让它保持打开状态,那么它会一直保持打开状态,直到超出范围并被垃圾回收。在这一点上,它可能会被安全地关闭(我相信 sqlite3 会这样做)。但最好还是安全第一,当你不再使用连接时,请关闭它们。 - Avaris
2
很高兴看到一个声望只有6的SO用户回来反驳那些他们认为没有回答问题的答案。这是一个大加赞赏。 - yurisich
6个回答

63

回答如果您不关闭SQLite数据库会发生什么的具体问题,答案非常简单,并适用于在任何编程语言中使用SQLite。当连接被代码显式关闭或程序退出隐式关闭时,任何未完成的事务都将被回滚(实际上是由下一个打开数据库的程序执行回滚)。如果没有未完成的事务,则不会发生任何事情。

这意味着您不需要过多地担心在进程退出前始终关闭数据库,并且您应该注意事务,确保在适当的点开始它们并提交。


12
长时间运行的进程(比如 Web 应用程序)在退出时没有隐式关闭,因为它没有退出。 - Jonathan Hartley

19

您的关切是有根据的,但了解sqlite的运作方式也同样重要:

1. connection open
    2. transaction started
        3. statement executes
    4. transaction done
5. connection closed

数据正确性而言,你只需要关注事务而不是打开的句柄。在sqlite中,仅在事务(*)或语句执行期间才会锁定数据库。

但是就资源管理而言,例如如果您计划删除sqlite文件或使用太多连接可能会耗尽文件描述符,则确实也要关注事务外的打开连接。

关闭连接有两种方法:一种是显式调用.close(),之后您仍然拥有句柄但无法使用它,另一种是让连接超出作用域并被垃圾回收。

如果您必须关闭连接,请明确地关闭它,遵循Python的座右铭" 明示比隐式更好 "。

如果您仅检查代码以获取副作用,则可以接受将最后一个变量保留对连接的引用并超出作用域,但请记住,异常会捕获堆栈,因此也会捕获该堆栈中的引用。如果传递异常,连接的生命周期可能会任意延长。

程序员的注意,sqlite默认使用“deferred”事务,即事务仅在执行语句时开始。在上面的例子中,事务从3到4运行,而不是从2到4。


2
我猜当使用内存数据库(sqlite3.connect(':memory:'))时,资源管理部分不适用?在这种情况下,我预计不会使用文件描述符。 - Jaanus Varus
1
@JaanusVarus,确实在使用“:memory:”数据库时不会分配文件描述符。但是我不能确定内部资源,毕竟是内存 :)请注意,当最后一个连接关闭时,内存中的数据库将被删除,因此对于OP用例和“:memory:”,他们必须保持至少1个连接打开以保留数据。 - Dima Tisnek

18
您可以像这样使用 with 块:
from contextlib import closing
import sqlite3

def query(self, db_name, sql):
    with closing(sqlite3.connect(db_name)) as con, con,  \
            closing(con.cursor()) as cur:
        cur.execute(sql)
        return cur.fetchall()
  • 连接数据库
  • 开始事务
  • 创建DB游标
  • 执行操作并返回结果
  • 关闭游标
  • 提交/回滚事务
  • 关闭连接

在所有情况下都是安全的,包括正常和异常情况。


2
这很棒。请注意,如果您执行的是不返回任何内容(例如插入或更新)的操作,则cur.fetchall()将只返回一个空列表。 - storm_m2138
1
这应该在SQLite文档中更加突出。我很生气自己没有意识到这应该是标准做法。非常感谢。 - CodingMatters

16

这是我使用的代码。 ConnectionCursor 将自动关闭,感谢 contextlib.closing()。由于上下文管理器,Connection 将自动提交。

import sqlite3
import contextlib

def execute_statement(statement):
    with contextlib.closing(sqlite3.connect(path_to_file)) as conn: # auto-closes
        with conn: # auto-commits
            with contextlib.closing(conn.cursor()) as cursor: # auto-closes
                cursor.execute(statement)

5
最内层的with有什么作用?释放一些内存吗?如果不使用该上下文管理器,可能会遇到什么问题?如果不返回cursor,那么仅从函数中返回是否等效? - Markus von Broady
1
@MarkusvonBroady 显式关闭 Cursor 对象是一种良好的实践,可以防止操作错误。有关更多信息,请参见此帖子 - howdoicode
1
@howdoicode 我没有看到你关于良好实践的说法有任何来源。我对这个想法持开放态度,但也持怀疑态度。自 OP 提出这个问题以来已经过去了十年,我们仍然没有一个权威的来源来说明这个主题的良好实践或深入解释为什么。我认为引用 Python 之禅中的明确性是有点牵强附会的,因为在 Python 中显式地释放动态数据或标记对象为不可用并不是惯用法。这就是 GC 和 __del__ 的作用。Dima 的倒数第二段话很令人不安,因为它暗示了语言的缺陷。 - Markus von Broady
抱歉有些晚了,但我也不明白外部或内部上下文语句的含义。当游标和连接超出作用域时,它们都会自动关闭,因此如果您不返回游标,只需要使用自动事务上下文块即可,对吧? - thegreatemu
我跳过最后一个with语句,而是在第二个with中直接使用conn.execute()try。官方Python文档中的§如何使用连接快捷方法指出,代码可以更简洁地编写,因为您不必显式地创建(通常是多余的)Cursor对象. §如何使用连接上下文管理器也是在_§ How-to guides_下的推荐阅读。 - legends2k

1

你的版本在连接使用后仍然保留了conn的作用域。

示例:

你的版本

    conn = sqlite3.connect(db_filename) #DECLARE CONNECTION OUT OF WITH BLOCK

    with conn:                          #USE CONNECTION IN WITH BLOCK
        cur = conn.cursor()
        cur.execute( ... )

   #conn variable is still in scope, so you can use it again

新版本

    with sqlite3.connect(db_filename) as conn:  #DECLARE CONNECTION AT START OF WITH BLOCK
        cur = conn.cursor()
        cur.execute( ... )   

   #conn variable is out of scope, so connection is closed 
   # MIGHT BE IT IS NOT CLOSED BUT WHAT  Avaris SAID!
   #(I believe auto close goes for with block)

24
with语句不会创建新的作用域。在两种情况下,conn都将在with之后可用。 - Avaris

-1

一般来管理数据库连接时,我会这样做:

# query method belonging to a DB manager class

def query (self, sql):
    con = sqlite3.connect(self.dbName)
    with con:
        cur = con.cursor()
        cur.execute(sql)
        res = cur.fetchall()
    if con:
        con.close()

    return res

这样做,我确信连接被明确关闭了。


6
如果出现异常,不关闭连接。 - Jurko Gospodnetić

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接