每次查询后重新打开sqlite数据库的效率

我目前正在龙卷风中处理一个Web服务器,但是我遇到了尝试同时访问数据库的不同代码的问题.

我通过简单地使用查询函数简化了这一点,该函数基本上是这样做的(但略高一点):

def query(command, arguments = []):
    db = sqlite3.open("models/data.db")
    cursor = db.cursor()
    cursor.execute(command, arguments)
    result = cursor.findall()
    db.close()
    return result

我只是想知道在每次查询后重新打开数据库是多么有效(我猜它是一个非常大的常量时间操作,还是会缓存一些东西?),以及是否有更好的方法来执行此操作.

我正在添加自己的答案,因为我不同意目前接受的答案.它声明该操作不是线程安全的,但这是完全错误的 – SQLite uses file locking适合其当前平台,以确保所有访问符合ACID.

在Unix系统上,这将是fcntl()或flock()锁定,这是一个每文件句柄锁.因此,每次发布一个新连接的代码将始终分配一个新的文件句柄,因此SQLite自己的锁定将防止数据库损坏.这样做的结果是在NFS共享或类似地方使用SQLite通常是个坏主意,因为这些通常不会提供特别可靠的锁定(但它确实取决于您的NFS实现).

正如@abernert在评论SQLite has had issues with threads中已经指出的那样,但这与在线程之间共享单个连接有关.正如他所提到的,这意味着如果您使用应用程序范围的池,如果第二个线程从池中提取回收连接,则会出现运行时错误.这些也是你在测试中可能没有注意到的那些烦人的错误(轻载,也许只有一个线程在使用中),但后来很容易引起头痛. Martijn Pieters后来建议使用线程本地池应该可以正常工作.

正如在版本3.3.1中的SQLite FAQ中所概述的那样,只要它们没有任何锁定,在线程之间传递连接实际上是安全的 – 这是SQLite的作者添加的让步,尽管它一般批评线程的使用.任何合理的连接池实现将始终确保在更换池中的连接之前已提交或回滚所有内容,因此实际上,如果不是针对共享的Python检查,则应用程序全局池可能是安全的.即使使用更新版本的SQLite,我相信仍然存在.当然我的Python 2.7.3系统有一个带有sqlite_version_info报告3.7.9的sqlite3模块,但是如果你从多个线程访问它,它仍然会抛出一个RuntimeError.

在任何情况下,当检查存在时,即使基础SQLite库支持连接,也无法有效地共享连接.

至于你原来的问题,每次创建新连接肯定比保持连接池效率低,但是已经提到过这需要一个线程本地池,实现起来有点痛苦.创建与数据库的新连接的开销基本上是打开文件并读取标头以确保它是有效的SQLite文件.实际执行语句的开销较高,因为它需要取出外观并执行相当多的文件I / O,因此大部分工作实际上是延迟到语句执行和/或提交之前.

然而,有趣的是,至少在Linux系统上我看过执行语句的代码重复了读取文件头的步骤 – 因此,打开一个新连接并不是那么糟糕,因为初始读取打开连接时,会将标头拉入系统的文件系统缓存中.因此,它归结为打开单个文件句柄的开销.

我还应该补充一点,如果你期望你的代码扩展到高并发性,那么SQLite可能是一个糟糕的选择.由于their own website points out它不适合高并发性,因为必须通过单个全局锁挤压所有访问的性能损失随着并发线程数量的增加而开始下降.如果你使用线程是为了方便,那很好,但是如果你真的期望高度的并发性,那么我就避免使用SQLite.

简而言之,我不认为你每次打开的方法实际上都是那么糟糕.线程本地池可以提高性能吗?可能是.这种性能的提升是否会引人注目?在我看来,除非你看到相当高的连接速率,否则你将拥有很多线程,所以你可能想要远离SQLite,因为它不能很好地处理并发性.如果您决定使用它,请确保在将连接返回到池之前清除连接 – SQLAlchemy具有一些您可能会觉得有用的功能,即使您不希望所有ORM层位于顶部.

编辑

非常合理地指出我应该附上真实的时间.这些来自相当低功耗的VPS:

>>> timeit.timeit("cur = conn.cursor(); cur.execute('UPDATE foo SET name=\"x\"
    WHERE id=3'); conn.commit()", setup="import sqlite3;
    conn = sqlite3.connect('./testdb')", number=100000)
5.733098030090332
>>> timeit.timeit("conn = sqlite3.connect('./testdb'); cur = conn.cursor();
    cur.execute('UPDATE foo SET name=\"x\" WHERE id=3'); conn.commit()",
    setup="import sqlite3", number=100000)
16.518677949905396

您可以看到差异大约3倍的因素,这并非无关紧要.但是,绝对时间仍然是亚毫秒级,因此除非您对每个请求进行大量查询,否则可能还有其他优先考虑的地方.如果您执行大量查询,则合理的折衷可能是每个请求的新连接(但没有池的复杂性,只需每次重新连接).

对于读取(即SELECT),每次连接的相对开销将更高,但挂钟时间的绝对开销应该是一致的.

正如在这个问题的其他地方已经讨论过的那样,你应该用真实的查询进行测试,我只是想记录一下我做出的结论.

翻译自:https://stackoverflow.com/questions/14511337/efficiency-of-reopening-sqlite-database-after-each-query

转载注明原文:每次查询后重新打开sqlite数据库的效率