在带有 sqlite 的 Python 中是不是有必要关闭游标?

Posted

技术标签:

【中文标题】在带有 sqlite 的 Python 中是不是有必要关闭游标?【英文标题】:In Python with sqlite is it necessary to close a cursor?在带有 sqlite 的 Python 中是否有必要关闭游标? 【发布时间】:2011-01-20 18:46:02 【问题描述】:

这里是场景。在您的函数中,您正在使用游标执行语句,但其中一个失败并引发异常。您的程序在关闭它正在使用的光标之前退出函数。光标会飘来飘去占用空间吗?我必须关闭光标吗?

此外,Python 文档有一个游标使用示例,并说:“如果我们完成了游标,我们也可以关闭它。”关键字是“可以”,而不是“必须”。这究竟是什么意思?

【问题讨论】:

【参考方案1】:

这可能是一个好主意(虽然它可能与 sqlite 无关,不知道那里,但它会让你的代码更具可移植性)。此外,使用最近的 Python (2.5+),这很容易:

from __future__ import with_statement
from contextlib import closing

with closing(db.cursor()) as cursor:
    # do some stuff

【讨论】:

【参考方案2】:

您不必在光标上调用close();它可以像任何其他对象一样被垃圾收集。

但是,即使等待垃圾回收听起来不错,我认为确保数据库游标等资源无论是否出现异常都被关闭仍然是一种不错的方式。

【讨论】:

【参考方案3】:

有趣的是,Python 3.0 doc 说“如果我们完成了光标,我们也可以关闭它”,而 Python 2.7 和 3.6 文档说“我们也可以关闭 连接如果我们完成了它”。

Python 2.7 和 3.0-3.4 文档没有描述游标 .close() 方法。但是 Python 3.5 和 3.6 文档描述了 cursor .close() 方法:

立即关闭光标(而不是在每次调用 __del__ 时)。

从现在开始,光标将无法使用;如果尝试对游标进行任何操作,将引发ProgrammingError 异常。

【讨论】:

【参考方案4】:

全部,

我在使用 sqlite3 的代码 (Python 3.8) 中遇到了逐渐的内存泄漏。我将可能的原因追溯到我的数据库类。事实证明,我会打开并使用游标,但从不关闭它。数据库在程序(Windows 服务)的生命周期内保持打开状态,并在退出时关闭。

一旦我开始关闭所有使用游标的数据库操作中的游标,我的内存泄漏就停止了,并且内存占用变得稳定。

因此,我建议您花时间关闭光标。它使代码更加一致,显然有助于控制内存消耗。

以下是我如何关闭光标的示例:

def write_to_db(self, cache_item:CacheEntry):
        '''Write a single cache entry to the database'''
        crsr = self._db_con.cursor()

        # Load some data elements
        fax_line_path = cache_item._dir_part
        phone_line = cache_item._phone_line
        sub_folder = cache_item._subfolder
        fname = cache_item._fname
        work_done = cache_item.get_workdone()

        try:
            crsr.execute(FilenameCacheDB.INSERT_CACHE,
                             (fax_line_path, 
                              phone_line, 
                              sub_folder, 
                              fname, 
                              work_done))

        except Exception as e:
            LOG.warning(f"Could not write cache_item to db because e")
            raise e

        finally:
            #
            # I was *not* closing the cursor prior
            #
            crsr.close()
            self._db_con.commit()

【讨论】:

【参考方案5】:

我还没有看到sqlite3.Cursor.close() 操作的任何效果。

关闭后,您仍然可以调用fetch(all|one|many),它将返回上一个执行语句的剩余结果。即使运行Cursor.execute() 仍然有效...

【讨论】:

我注意到了同样的行为(我编写了一个测试以确保光标已关闭,但它失败了),并想知道这是 python 连接器问题还是 sqlite3 固有的问题。【参考方案6】:

此代码将自动关闭Cursor。它还会自动关闭并提交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)

【讨论】:

为什么我不能只使用with con.cursor() as cursor: ...?那会更容易阅读..这段代码感觉有点像用java编程..嗯/shrug【参考方案7】:

看代码sn-p和***user2010和Peer给出的思路,使用Python contextmanager优雅地处理游标更容易。

from contextlib import contextmanager

@contextmanager
def OpenCursor(conn):
    cursor = conn.cursor()
    try:    
        yield (cursor)
    except Exception as e:  
        cursor.close()  
        raise e
    else:                     
        cursor.close() 

在没有 OpenCursor 的情况下使用:

def get(conn, key, default=None):
    cursor = conn.cursor()
    cursor.execute(f'SELECT value FROM table WHERE key=?', (key,))
    row = cursor.fetchone()
    if row:
        return (True)
    else:
        return (default)

使用 OpenCursor 作为上下文管理器:

def get(conn, key, default=None):
    with OpenCursor(conn) as cursor:
        cursor.execute(f'SELECT value FROM table WHERE key=?', (key,))
        row = cursor.fetchone()
        if row:
            return (True)
        else:
            return (default)

【讨论】:

以上是关于在带有 sqlite 的 Python 中是不是有必要关闭游标?的主要内容,如果未能解决你的问题,请参考以下文章

Python sqlite3 构建带有动态占位符的部分

如何在 Ubuntu 16.04 上将 FTS5 扩展与带有 Python 3.7 的 sqlite3 python 模块一起使用?

如何插入带有 PDO 扩展的 SQLite?

带有 Sqlite 的 Android - 如何检查表是不是存在以及是不是为空

Sqlite - 从行“专辑”中获得不同的结果 - python

SQLite 是不是支持参照完整性?