Python sqlite3 的连接上下文:with 已经结束,为什么数据库连接仍然能查询

前天 3阅读

把 sqlite3.connect 放进 with,导入结束后继续执行查询居然仍然成功。很多人从文件对象的习惯推断,离开 with 就一定关闭了资源,但连接对象的上下文协议承担的是事务处理,不负责关闭连接。它是否提交或回滚与连接是否还可使用,是需要分别检查的两件事。

本例明确要求 Python 三点十二或更新版本,并给连接传入 autocommit=False,不依赖随版本可能改变的默认事务模式。数据库只存在于内存。保存为 demo.py 后运行 python demo.py,先验证普通连接上下文的行为,再用 closing 建立真正的连接生命周期。

Python sqlite3 的连接上下文:with 已经结束,为什么数据库连接仍然能查询

AI生成概念插图:事务完成标记已经出现,连接通道仍保持打开,需要另行关闭;不是数据库平台或运行截图。

import sqlite3
from contextlib import closing

db = sqlite3.connect(":memory:", autocommit=False)
try:
    with db:
        db.execute("CREATE TABLE item(id INTEGER PRIMARY KEY)")
        db.execute("INSERT INTO item VALUES(1)")
    count = db.execute("SELECT count(*) FROM item").fetchone()[0]
    assert count == 1
    print("outside with:", count)
    try:
        with db:
            db.execute("INSERT INTO item VALUES(2)")
            raise RuntimeError("cancel this batch")
    except RuntimeError:
        pass
    count = db.execute("SELECT count(*) FROM item").fetchone()[0]
    assert count == 1
    print("after rollback:", count)
finally:
    db.close()

def assert_closed(connection, label):
    try:
        connection.execute("SELECT 1")
    except sqlite3.ProgrammingError:
        print(label, "closed")
    else:
        raise AssertionError("connection is still open")

assert_closed(db, "first connection")
with closing(sqlite3.connect(":memory:", autocommit=False)) as other:
    with other:
        other.execute("CREATE TABLE note(value TEXT)")
        other.execute("INSERT INTO note VALUES(?)", ("saved",))
    assert other.execute("SELECT value FROM note").fetchone() == ("saved",)
assert_closed(other, "second connection")

块外还能查询,是协议本来允许的结果

第一次 with 内创建表并写入一条记录,退出之后 outside with 输出一,证明连接仍然可用。第二个 with 写入另一条记录后主动抛出 RuntimeError,块外捕获异常,再查询只剩第一条记录,输出 after rollback: 1。这里既验证了之前保存的数据仍在,也验证了失败那次改动没有留下。

示例随后显式 close,再尝试执行 SELECT,得到 ProgrammingError,打印 first connection closed。只有这一步检查的是关闭状态。不要把异常回滚、成功提交或者游标读完当作关闭连接的替代证据,它们都可能发生在一个继续被复用的连接上。

在 autocommit=False 模式下,提交或回滚后还会隐式开启新的事务,因此也不能用“是否还在事务中”直接判断是否已经完成上一段工作。本文没有用这个标志判断关闭,而是把资源的可用性和事务结果分别验证,避免把不同层面的状态压成一个布尔值。

把连接所有权写进最外层结构

第二部分最外层使用 contextlib.closing,里面才是连接本身的 with。内层正常退出处理事务,外层退出调用 close,顺序在代码结构中直接可见。离开最外层后再次查询,确认 second connection closed。即使内层抛出异常,外层也会在退出时执行关闭;异常本身仍应由业务调用者按需求处理。

不要只保留外层 closing 就以为成功结果一定提交。closing 的职责是调用对象的 close 方法,不会凭空替你承诺提交。对于这里明确设置的事务模式,未提交变化在关闭时会被回滚。是否先提交、何时失败回滚,应放在事务管理层,不能依靠程序退出时的收尾顺便决定。

反过来,共享给多项业务操作的连接,也不应在每个辅助函数里随手套 closing。一个实用约定是由创建连接的层负责最终关闭,接收现有连接的辅助函数说明自己是否拥有事务边界。否则某个函数表面上只做一次查询,却提前关闭了调用者还要继续使用的资源。

如果选择 autocommit=True,连接上下文对事务不执行本例这种提交或回滚动作,不能把模式改掉后仍期待相同失败结果。较旧解释器没有这个关键字时,应阅读对应版本的事务控制说明并重写实验,而不是直接删除参数,留下未经核对的默认行为。

最后,内存库适合验证协议与清理顺序,不是磁盘持久性测试。本文没有模拟断电、锁竞争或提交设备错误。实际导入程序可以沿用两个独立验收点:数据是否按约定保存,以及拥有连接的层是否已释放资源。两项都明确,才方便追踪“任务结束了却还占着连接”的问题。

资料核对日期:2026年10月2日(北京时间)。示例在本地 Python 3.12.14、SQLite 3.53.1 实际运行并通过断言,结果仅对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。