在使用Python和sqlite时,是否有必要关闭游标?

56
这是一个场景。在您的函数中,您正在使用光标执行语句,但其中一个语句失败并引发异常。在关闭它正在使用的光标之前,程序退出该函数。这个光标会漂浮并占用空间吗?我需要关闭光标吗?
此外,Python文档提供了一个光标使用示例,并说:“如果我们完成了工作,我们也可以关闭光标。” 关键词是“可以”,而不是“必须”。他们的意思是什么?
9个回答

26

这可能是个好主意(尽管在sqlite上可能并不重要,我不确定),但它会使您的代码更具可移植性。此外,对于最近版本的Python(2.5+),这很容易实现:

from __future__ import with_statement
from contextlib import closing

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

15

您不必调用close()关闭光标;它可以像其他对象一样被垃圾回收。

但即使等待垃圾回收听起来没问题,我认为仍然有好的风格是确保诸如数据库游标之类的资源在是否出现异常的情况下都能得到关闭。


14

大家好,

我使用sqlite3和Python 3.8编写代码时遇到了逐渐增加的内存泄漏问题。我追踪了问题的根源并发现是我的数据库类,我打开了一个游标但从未关闭它。数据库在程序(Windows服务)运行期间一直保持打开状态,并在退出时关闭。

在我开始关闭所有使用游标的db中的游标之后,我的内存泄漏停止了,并且内存足迹变得稳定。

因此,我建议你花时间关闭你的游标。这将使代码更加一致,而且显然有助于控制内存消耗。

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

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()

9

这段代码将自动关闭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)

5
为什么我不能只使用 with con.cursor() as cursor: ...?那样会更容易阅读。这段代码感觉有点像在编写Java程序... 嗯 /耸肩 - matthaeus
@matthaeus:这比标准的conn = sqlite3.connect(...); cur = conn.cursor(); cur.execute(...); cur.close(); conn.commit(); conn.close()更美观,而且更短、更安全。如果使用sqlite3的conn.execute()快捷方式,它甚至可以更短。我也希望Python的DB API能够自动关闭Cursor的上下文管理器。 - MestreLion

9
有趣的是,Python 3.0文档说“如果我们完成了对光标的操作,我们也可以关闭它”,而Python 2.73.6文档则说“如果我们完成了对连接的操作,我们也可以关闭连接”。
Python 2.7和3.0-3.4文档没有描述光标.close()方法。但是Python 3.5和3.6文档描述了光标.close()方法:

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

从此时起,光标将无法使用,如果尝试使用光标进行任何操作,将引发ProgrammingError异常。


7

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

关闭之后,你仍然可以调用 fetch(all|one|many) 方法,它会返回上一次执行语句剩余的结果。甚至运行 Cursor.execute() 也仍然有效...


1
我注意到了相同的行为(我编写了一个测试来确保光标已关闭,但它失败了),想知道这是Python连接器问题还是sqlite3本身的问题。 - haridsv
我现在没有看到那种行为。尝试它会引发异常,sqlite3.ProgrammingError: Cannot operate on a closed cursor. - andrewdotn
的确。Cursor.close自此提交后实际上关闭了游标,这发生在我最初回答之前不久,而且显然已经进入Python 2.7。感谢更新! - Simon A. Eugster

2

看着stackoverflowuser2010和Peer提供的代码片段和想法,使用Python上下文管理器优雅地处理游标更加容易。

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)

使用 finally: cursor.close() 替代 except/else 会使 OpenCursor() 的代码变得更简单(也更安全)。实际上,整个函数可以成为一个一行代码的函数,不需要装饰器,并且具有完全相同的功能:def OpenCursor(conn): return contextlib.closing(conn.cursor()) - MestreLion

0

首先,什么是Python的sqlite3.Cursor代码显示它本质上是一个sqlite3_stmt结构的持有者,这是一个准备好的语句句柄,它被传递给sqlite3_step来迭代结果行。

据我所知,使用 conn.execute()自动创建并返回的游标对象,与使用 conn.cursor() 创建自己的游标对象相比没有任何好处。跳过 conn.cursor() ,这样每次可以节省一行代码。(这是特定于sqlite的建议;对于其他数据库系统的其他DB-API驱动程序,可能有创建自己的游标的原因,我不知道。)

cursor.close() 是什么意思?

static PyObject *
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
{
    ⋮
    if (self->statement) {
    (void)stmt_reset(self->statement);
    Py_CLEAR(self->statement);
    ⋮
}

SQLite文档简要介绍了sqlite3_reset,它是stmt_reset调用的函数

6. 绑定参数和重用预处理语句

SQLite 允许同一个预处理语句被多次执行。这可以通过以下例程实现:

预处理语句经过一次或多次sqlite3_step()调用后,可以通过调用sqlite3_reset()将其重置以便再次执行。可以将sqlite3_reset()视为将预处理语句程序倒回到开头。

关闭游标会导致Python告诉底层的sqlite库丢弃相关的结果集。但是Python仍然保留sqlite3_stmt结构,因为Python内部维护了一个准备语句的缓存。
像下面这样的代码在CPython上通常是可以的:
for row in conn.execute("SELECT …"):
    do_stuff_with(r)

这是因为:
1. 当迭代到结果集的末尾时,Python会自动调用stmt_reset来丢弃结果集。这适用于标准的Python迭代协议,如for ... in ...,以及sqlite3.Cursor.fetch*方法。
2. 如果循环提前退出,例如因为引发了异常,CPython的引用计数将触发匿名游标对象的终止,再次调用stmt_reset。其他Python实现可能会有所不同。
鉴于上述情况,虽然你不关闭游标不太可能遇到内存泄漏,但如果你有游标既:
1. 只部分迭代,且 2. 长时间保持在作用域中,
那么内存泄漏就有可能发生。

因此,为了实现可移植性和明确性,您可能希望编写以下代码:

from contextlib import closing

with closing(conn.execute("SELECT …")) as cursor:
    for row in cursor:
        …

0

是的,我们应该关闭我们的游标。我曾经遇到过一个错误,当我使用游标来配置我的连接对象时:'PRAGMA synchronous=off' 和 'PRAGMA journal_mode=off' 以加快插入速度。一旦我关闭了游标,错误消失了。我忘记了我遇到了什么类型的错误。


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