【问题标题】:Why close a cursor for Sqlite3 in Python为什么在 Python 中关闭 Sqlite3 的游标
【发布时间】:2017-10-11 04:29:43
【问题描述】:

使用 Python 的 sqlite3 module 时关闭游标有什么好处吗?或者它只是 DB API v2.0 的一个工件,可能只对其他数据库有用?

connection.close() 释放资源是有道理的;但是,不清楚 cursor.close() 究竟做了什么,它实际上是释放了一些资源还是什么也不做。它的文档没有启发性:

>>> import sqlite3
>>> conn = sqlite3.connect(':memory:')
>>> c = conn.cursor()
>>> help(c.close)
Help on built-in function close:

close(...)
    Closes the cursor.

注意,这是一个与Why do you need to create a cursor when querying a sqlite database? 完全不同的问题。我知道游标是干什么用的。问题是cursor.close() 方法实际上做了什么以及调用它是否有任何好处。

【问题讨论】:

标签: python sqlite python-internals database-cursor python-db-api


【解决方案1】:

分析

CPython _sqlite3.Cursor.close 对应于 pysqlite_cursor_close,除了一些健全性检查并将其标记为已关闭之外,does this

if (self->statement) {
    (void)pysqlite_statement_reset(self->statement);
    Py_CLEAR(self->statement);
}

pysqlite_statement_reset 依次从 SQLite 的 C API 调用 sqlite3_reset

调用 sqlite3_reset() 函数将准备好的语句对象重置回其初始状态,准备重新执行。任何使用 sqlite3_bind_*() API 绑定了值的 SQL 语句变量都会保留它们的值。使用 sqlite3_clear_bindings() 重置绑定。

[...]

sqlite3_reset(S) 接口不会更改准备好的语句 S 上任何绑定的值。

Prepared Statement Object API 用于绑定参数,例如在_sqlite3.Cursor.execute。因此,如果使用sqlite3_clear_bindings,它可能已经能够释放一些用于存储参数的内存,但我没有看到它在 CPython/pysqlite 中的任何地方调用。

实验

我使用memory-profiler 绘制内存使用图表并生成逐行报告。

import logging
import sqlite3
import time

# For the function brackets to appear on the chart leave this out:
#
#     If your Python file imports the memory profiler 
#     "from memory_profiler import profile" these timestamps will not be
#     recorded. Comment out the import, leave your functions decorated, 
#     and re-run.
#
# from memory_profiler import profile


class CursorCuriosity:
  
    cursor_num = 20_000
    param_num = 200
    
    def __init__(self):
        self.conn = sqlite3.connect(':memory:')
        self.cursors = []
    
    @profile
    def create(self):
        logging.info('Creating cursors')
        sql = 'SELECT {}'.format(','.join(['?'] * self.param_num))
        for i in range(self.cursor_num):
            params = [i] * self.param_num
            cur = self.conn.execute(sql, params)
            self.cursors.append(cur)
    
    @profile
    def close(self):
        logging.info('Closing cursors')
        for cur in self.cursors:
            cur.close()

    @profile
    def delete(self):
        logging.info('Destructing cursors')
        self.cursors.clear()
    
    @profile    
    def disconnect(self):
        logging.info('Disconnecting')
        self.conn.close()
        del self.conn


@profile
def main():
    curcur = CursorCuriosity()
    
    logging.info('Sleeping before calling create()')
    time.sleep(2)
    curcur.create()
    
    logging.info('Sleeping before calling close()')
    time.sleep(2)
    curcur.close()
    
    logging.info('Sleeping before calling delete()')
    time.sleep(2)
    curcur.delete()
    
    logging.info('Sleeping before calling disconnect()')
    time.sleep(2)
    curcur.disconnect()
    
    logging.info('Sleeping before exit')
    time.sleep(2)  


if __name__ == '__main__':
    logging.basicConfig(level='INFO', format='%(asctime)s %(message)s')
    main()

我首先运行它,将 profile 导入注释掉以获取情节。

mprof run -T 0.05 cursor_overhead.py
mprof plot

然后用import在终端得到输出。

mprof run -T 0.05 cursor_overhead.py
Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    51     19.1 MiB     19.1 MiB           1   @profile
    52                                         def main():
    53     19.1 MiB      0.0 MiB           1       curcur = CursorCuriosity()
    54                                             
    55     19.1 MiB      0.0 MiB           1       logging.info('Sleeping before calling create()')
    56     19.1 MiB      0.0 MiB           1       time.sleep(2)
    57   2410.3 MiB   2391.2 MiB           1       curcur.create()
    58                                             
    59   2410.3 MiB      0.0 MiB           1       logging.info('Sleeping before calling close()')
    60   2410.3 MiB      0.0 MiB           1       time.sleep(2)
    61   2410.3 MiB      0.0 MiB           1       curcur.close()
    62                                             
    63   2410.3 MiB      0.0 MiB           1       logging.info('Sleeping before calling delete()')
    64   2410.3 MiB      0.0 MiB           1       time.sleep(2)
    65   1972.2 MiB   -438.1 MiB           1       curcur.delete()
    66                                             
    67   1972.2 MiB      0.0 MiB           1       logging.info('Sleeping before calling disconnect()')
    68   1972.2 MiB      0.0 MiB           1       time.sleep(2)
    69   1872.7 MiB    -99.5 MiB           1       curcur.disconnect()
    70                                             
    71   1872.7 MiB      0.0 MiB           1       logging.info('Sleeping before exit')
    72   1872.7 MiB      0.0 MiB           1       time.sleep(2) 

以及用于完整性的个人方法。

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    24     19.1 MiB     19.1 MiB           1       @profile
    25                                             def create(self):
    26     19.1 MiB      0.0 MiB           1           logging.info('Creating cursors')
    27     19.1 MiB      0.0 MiB           1           sql = 'SELECT {}'.format(','.join(['?'] * self.param_num))
    28   2410.3 MiB      0.0 MiB       20001           for i in range(self.cursor_num):
    29   2410.1 MiB      0.0 MiB       20000               params = [i] * self.param_num
    30   2410.3 MiB   2374.3 MiB       20000               cur = self.conn.execute(sql, params)
    31   2410.3 MiB     16.9 MiB       20000               self.cursors.append(cur)
Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    33   2410.3 MiB   2410.3 MiB           1       @profile
    34                                             def close(self):
    35   2410.3 MiB      0.0 MiB           1           logging.info('Closing cursors')
    36   2410.3 MiB      0.0 MiB       20001           for cur in self.cursors:
    37   2410.3 MiB      0.0 MiB       20000               cur.close()
Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    39   2410.3 MiB   2410.3 MiB           1       @profile
    40                                             def delete(self):
    41   2410.3 MiB      0.0 MiB           1           logging.info('Destructing cursors')
    42   1972.2 MiB   -438.1 MiB           1           self.cursors.clear()
Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    44   1972.2 MiB   1972.2 MiB           1       @profile    
    45                                             def disconnect(self):
    46   1972.2 MiB      0.0 MiB           1           logging.info('Disconnecting')
    47   1972.2 MiB      0.0 MiB           1           self.conn.close()
    48   1872.7 MiB    -99.5 MiB           1           del self.conn

结论

  1. 关闭 sqlite3.Cursor 不会释放内存(但会做一些工作,操作 SQLite 准备好的语句的状态)
  2. 删除/销毁游标可以释放内存
  3. 删除/销毁 sqlite3.Connection 可以释放内存(关闭不会)

【讨论】:

    【解决方案2】:

    就 SQLite 而言,没有太大区别,但数据库的 API 不仅适用于嵌入式数据库,还适用于所有 SQL 数据库。

    对于 DBMS,游标通常意味着客户端中的会话,有时也意味着服务器上的会话。

    因此,如果您没有使用 Python 的引用计数实现(例如 CPython),那么在 GC 释放它们之前,可能会占用大量资源。

    【讨论】:

      猜你喜欢
      • 2017-12-25
      • 1970-01-01
      • 1970-01-01
      • 2014-08-07
      • 2010-09-22
      • 2015-03-08
      • 1970-01-01
      • 2017-01-29
      • 2013-06-29
      相关资源
      最近更新 更多