在带有SQLite的Python中,是否有必要关闭游标?

gfttwv5a  于 2022-11-15  发布在  SQLite
关注(0)|答案(9)|浏览(296)

以下是场景。在您的函数中,您正在使用游标执行语句,但其中一个语句失败并引发异常。您的程序在关闭正在使用的游标之前退出函数。光标会不会四处漂浮,占据空间?我必须关闭光标吗?
此外,Python文档还提供了一个使用游标的示例,并表示:“如果使用完游标,我们也可以关闭它。”关键字是“可以”,而不是“必须”。他们这么说到底是什么意思?

jaql4c8m

jaql4c8m1#

这可能是个好主意(虽然它对于SQLite可能不是很重要,我不知道它在哪里,但它会使您的代码更可移植)。此外,使用最新的Python(2.5+),很容易做到:

from __future__ import with_statement
from contextlib import closing

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

ogsagwnx2#

您不必在游标上调用close();它可以像任何其他对象一样被垃圾回收。
但是,即使等待垃圾收集听起来没问题,我认为确保诸如数据库游标之类的资源被关闭(无论是否存在异常)仍然是一种不错的风格。

pgpifvop

pgpifvop3#

全,
我在使用Sqlite3的代码(Python3.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()
jv2fixgn

jv2fixgn4#

有趣的是,Python 3.0 doc说“如果我们完成它,我们也可以关闭它”,而Python 2.73.6文档说“如果我们完成它,我们也可以关闭*连接”。
Python2.7和3.0-3.4文档没有描述游标.close()方法。但Python3.5和3.6文档描述了游标.close()方法:
立即关闭光标(而不是每次调用__del__时)。
从这一点开始,游标将不可用;如果尝试对游标执行任何操作,将引发ProgrammingError异常。

uelo1irk

uelo1irk5#

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

ppcbkaq56#

我还没有看到sqlite3.Cursor.close()操作的任何效果。
关闭后,您仍然可以调用fetch(all|one|many),它将返回上一条EXECUTE语句的剩余结果。即使运行Cursor.execute()也能正常工作...

3pvhb19x

3pvhb19x7#

查看Stackoverflow User2010和Peer提供的代码片段和想法,使用PythonConextManager可以更轻松地优雅地处理游标。

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

kr98yfug8#

是的,我们应该关闭我们的光标。我曾经在使用游标配置连接对象时遇到错误:‘Pragma Synchronous=Off’和‘Pragma JONERNAL_MODE=Off’以加快插入速度。一旦我关闭了光标,错误就消失了。我忘了我遇到了什么类型的错误。

0lvr5msh

0lvr5msh9#

首先,什么是Pythonsqlite3.Cursor?代码显示它本质上是sqlite3_stmt结构的持有者,passed to sqlite3_steppassed to sqlite3_step迭代结果行的预准备语句句柄。
就我所知,使用conn.cursor()创建您自己的Cursor对象,而不是仅仅使用为您自动创建并由conn.execute()返回的对象,是没有好处的。跳过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允许多次评估相同的prepared statement版本。这是使用以下例程完成的:

在通过一次或多次调用sqlite3_step()prepared statement进行求值之后,可以对其进行重置,以便通过对sqlite3_reset()的调用再次进行求值。可以将sqlite3_reset()看作是将prepared statement程序倒回到开头。
关闭游标会导致Python通知底层的SQLite库丢弃相关的结果集。但是,Python仍然保留sqlite3_stmt结构,因为它在内部维护已准备好的语句的缓存。
如下所示的代码在CPython上通常很好用:

for row in conn.execute("SELECT …"):
    do_stuff_with(r)

这是因为:
1.当迭代到达结果集末尾时,python会自动调用stmt_reset丢弃结果集。这发生在for … in …sqlite3.Cursor.fetch*方法等标准的Python迭代协议中。
1.如果循环提前退出,例如因为引发异常,CPython的引用计数将触发匿名游标对象的终结,再次调用stmt_reset。其他的Python实现可能有所不同。
考虑到上述情况,虽然不关闭游标导致内存泄漏的可能性不大,但如果游标同时,则绝对“有可能”:

  • 仅部分迭代,并且
  • 长时间保持在范围内。

因此,为了便于移植和明确,您可以这样写:

from contextlib import closing

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

相关问题