我最近在涉及大量数据处理的项目中频繁使用 sqlite3。我最初的尝试根本不涉及任何数据库,全部的数据都将保存在内存中,包括字典查找、迭代和条件等查询。这很好,但能够放入内存的只有那么多,而且将数据从磁盘从新生成或加载到内存是一个繁琐又耗时的过程。html
我决定试一试sqlite3。由于只需打开与数据库的链接,这样能够增长可处理的数据量,并将应用程序的加载时间减小到零。此外,我能够经过 SQL 查询替换不少Python逻辑语句。python
我想分享一些关于此次经历的心得和发现。git
TL;DRgithub
若是你须要在数据库中一次性插入不少行,那么你真不该该使用 execute。sqlite3 模块提供了批量插入的方式:executemany。sql
而不是像这样作:数据库
1
2
|
for row in iter_data():
connection.execute('INSERT INTO my_table VALUES (?)', row)
|
你能够利用这个事实,即 executemany 接受元组的生成器做为参数:安全
1
2
3
4
|
connection.executemany(
'INSERT INTO my_table VALUE (?)',
iter_data()
)
|
这不只更简洁,并且更高效。实际上,sqlite3 在幕后利用 executemany 实现 execute,但后者插入一行而不是多行。性能
我写了一个小的基准测试,将一百万行插入空表(数据库在内存中):测试
一开始我常常搞混的事情就是,光标管理。在线示例和文档中一般以下:fetch
1
2
3
|
connection = sqlite3.connect(':memory:')
cursor = connection.cursor()
# Do something with cursor
|
但大多数状况下,你根本不须要光标,你能够直接使用链接对象(本文末尾会提到)。
像execute和executemany相似的操做能够直接在链接上调用。如下是一个证实此事的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import sqlite3
connection = sqlite3(':memory:')
# Create a table
connection.execute('CREATE TABLE events(ts, msg)')
# Insert values
connection.executemany(
'INSERT INTO events VALUES (?,?)',
[
(1, 'foo'),
(2, 'bar'),
(3, 'baz')
]
)
# Print inserted rows
for row in connnection.execute('SELECT * FROM events'):
print(row)
|
你可能常常会看到使用fetchone或fetchall来处理SELECT查询结果的示例。可是我发现处理这些结果的最天然的方式是直接在光标上迭代:
1
2
|
for row in connection.execute('SELECT * FROM events'):
print(row)
|
这样一来,只要你获得足够的结果,你就能够终止查询,而且不会引发资源浪费。固然,若是事先知道你须要多少结果,能够改用LIMIT SQL语句,但Python生成器是很是方便的,可让你将数据生成与数据消耗分离。
即便在处理SQL事务的中间,也会发生讨厌的事情。为了不手动处理回滚或提交,你能够简单地使用链接对象做为上下文管理器。 在如下示例中,咱们建立了一个表,并错误地插入了重复的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import sqlite3
connection = sqlite3.connect(':memory:')
with connection:
connection.execute(
'CREATE TABLE events(ts, msg, PRIMARY KEY(ts, msg))')
try:
with connection:
connection.executemany('INSERT INTO events VALUES (?, ?)', [
(1, 'foo'),
(2, 'bar'),
(3, 'baz'),
(1, 'foo'),
])
except (sqlite3.OperationalError, sqlite3.IntegrityError) as e:
print('Could not complete operation:', e)
# No row was inserted because transaction failed
for row in connection.execute('SELECT * FROM events'):
print(row)
connection.close()
|
…当它真的有用时
在你的程序中有几个 pragma 可用于调整 sqlite3 的行为。特别地,其中一个能够改善性能的是synchronous:
1
|
connection.execute('PRAGMA synchronous = OFF')
|
你应该知道这多是危险的。若是应用程序在事务中间意外崩溃,数据库可能会处于不一致的状态。因此请当心使用! 可是若是你要更快地插入不少行,那么这多是一个选择。
假设你须要在数据库上建立几个索引,而你须要在插入不少行的同时建立索引。把索引的建立推迟到全部行的插入以后能够致使实质性的性能改善。
使用 Python 字符串操做将值包含到查询中是很方便的。可是这样作很是不安全,而 sqlite3 给你提供了更好的方法来作到这一点:厦门叉车
1
2
3
4
5
6
7
|
# Do not do this!
my_timestamp = 1
c.execute("SELECT * FROM events WHERE ts = '%s'" % my_timestamp)
# Do this instead
my_timestamp = (1,)
c.execute('SELECT * FROM events WHERE ts = ?', my_timestamp)
|
此外,使用Python%s(或格式或格式的字符串常量)的字符串插值对于executemany来讲并非老是可行。因此在此尝试没有什么真正意义!
请记住,这些小技巧可能会(也可能不会)给你带来好处,具体取决于特定的用例。你应该永远本身去尝试,决定是否值得这么作。