使用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()方法实际执行的操作以及调用它是否有任何好处。
答案 0 :(得分: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
然后通过导入在终端中获取输出。
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
sqlite3.Cursor
不会释放内存(但会做一些工作,操纵 SQLite 准备好的语句的状态)sqlite3.Connection
会释放内存(关闭不会)答案 1 :(得分:0)
对于SQLite,没有太大区别,但数据库的API不仅适用于嵌入式数据库,也适用于所有SQL数据库。
对于DBMS,游标通常意味着客户端中的会话,有时在服务器上。
因此,如果您没有使用Python的引用计数实现(例如CPython),那么在GC释放它们之前可能会占用大量资源。