使用 sqlite3 包在 python 中的不同线程之间共享一个:memory: 数据库

Posted

技术标签:

【中文标题】使用 sqlite3 包在 python 中的不同线程之间共享一个:memory: 数据库【英文标题】:sharing a :memory: database between different threads in python using sqlite3 package 【发布时间】:2011-03-19 21:45:42 【问题描述】:

我想在 python 中创建一个 :memory: 数据库并从不同的线程访问它。 本质上是这样的:

class T(threading.Thread):
    def run(self):
        self.conn = sqlite3.connect(':memory:')
        # do stuff with the database

for i in xrange(N):
    T().start()

并让所有连接都引用同一个数据库。

我知道将check_same_thread=True 传递给连接函数并共享 线程之间的连接,但如果可能的话希望避免这样做。感谢您的帮助。

编辑:更正了一个错字。我最初说“让所有连接都引用同一个线程”用线程代替数据库。

【问题讨论】:

您能描述一下需要这样做的场景吗?除了在多个线程中使用 sqlite 之外,还有其他选择吗? @Muhammad Alkarouri 我需要它来对多线程数据库应用程序进行单元测试。如果使用一个文件(就像在实际应用程序中那样),那么我可以打开多个连接,如果就好了。我最终将数据库逻辑包装在一个线程中,该线程使用消费者模式并返回它在收到请求时填充的延迟。 【参考方案1】:

SQLite 在过去 4 年中得到了改进,因此现在可以使用共享内存数据库。检查以下代码:

import sqlite3

foobar_uri = 'file:foobar_database?mode=memory&cache=shared'
not_really_foobar_uri = 'file:not_really_foobar?mode=memory&cache=shared'

# connect to databases in no particular order
db2 = sqlite3.connect(foobar_uri, uri=True)
db_lol = sqlite3.connect(not_really_foobar_uri, uri=True)
db1 = sqlite3.connect(foobar_uri, uri=True)

# create cursor as db2
cur2 = db2.cursor()

# create table as db2
db2.execute('CREATE TABLE foo (NUMBER bar)')

# insert values as db1
db1.execute('INSERT INTO foo VALUES (42)')
db1.commit()

# and fetch them from db2 through cur2
cur2.execute('SELECT * FROM foo')
print(cur2.fetchone()[0])  # 42

# test that db_lol is not shared with db1 and db2
try:
    db_lol.cursor().execute('SELECT * FROM foo')
except sqlite3.OperationalError as exc:
    print(exc)  # just as expected

数据库访问被有意地纠缠在一起,以表明从 SQLite 的角度来看,两个同名的内存数据库连接是相同的。

参考资料:

    SQLite URIs SQLite shared cache

不幸的是,URI 连接仅在 Python 3.4 之后可用。但是,如果你有 Python 2.6 或更高版本(但不是 Python 3),内置的sqlite3 模块仍然能够导入 APSW 连接,可以使用它来达到相同的效果。这是插入式sqlite3 模块替换:

from sqlite3 import *
from sqlite3 import connect as _connect
from apsw import Connection as _ApswConnection
from apsw import SQLITE_OPEN_READWRITE as _SQLITE_OPEN_READWRITE
from apsw import SQLITE_OPEN_CREATE as _SQLITE_OPEN_CREATE
from apsw import SQLITE_OPEN_URI as _SQLITE_OPEN_URI

# APSW and pysqlite use different instances of sqlite3 library, so initializing
# APSW won't help pysqlite. Because pysqlite does not expose any way to
# explicitly call sqlite3_initialize(), here goes an ugly hack. This only has
# to be done once per process.
_connect(':memory:').close()

def connect(database, timeout=5.0, detect_types=0, isolation_level=None,
            check_same_thread=True, factory=Connection, cached_statements=100,
            uri=False):
    flags = _SQLITE_OPEN_READWRITE | _SQLITE_OPEN_CREATE

    if uri:
        flags |= _SQLITE_OPEN_URI

    db = _ApswConnection(database, flags, None, cached_statements)
    conn = _connect(db, timeout, detect_types, isolation_level, 
                    check_same_thread, factory, cached_statements)

    return conn

【讨论】:

非常感谢!我正在查看 python 3.3 文档,所以我忽略了 sqlite3.connect()uri 选项,该选项仅 自 Python 3.4 起才可用 对于仍在使用 Python 2.7 的人来说,最好的选择(如果有的话)是什么? 不幸的是,这导致我的 python 崩溃....将更新到 Python 3,除非您对我可以从不同线程访问的内存数据库有其他建议 @baconwichsand 如果它崩溃了,请三重检查你有没有抛出_connect(':memory:').close() 行——与pysqlite 建立一次连接至关重要,因为它会隐式调用sqlite3_initialize()。如果没有这一行,pysqlite 的 sqlite 实例将不会创建互斥锁,并且会在尝试锁定连接时死亡。 @baconwichsand 如果它仍然会崩溃,请告诉您的操作系统和确切的 Python 版本,我会更新答案以反映您的情况。【参考方案2】:

如果不破解 sqlite3 库本身,您将无法重用 :memory: 数据库,因为它保证对于每个连接都是独占和私有的。要破解它的访问权限,请仔细查看 sqlite3 分发版(不是 Python 模块分发版)中的 src/pager.c。也许,实现这一点的最方便的方法是通过一些简单的 C 端映射使 :memory:00:memory:something:memory:okay_hai 等别名来处理不同的 pPager->memDb 指针。

【讨论】:

您好,近 4 年后有什么新信息吗? Python中当前的sqlite3仍然不可能吗? @compostus,现在可以指定数据库 URI,参考这个sqlite.org/uri.html,查找mode=memorycache=shared @compostus,看看我对这个问题的新回答。

以上是关于使用 sqlite3 包在 python 中的不同线程之间共享一个:memory: 数据库的主要内容,如果未能解决你的问题,请参考以下文章

使用 `executemany` 更新现有 SQLite3 数据库中的条目(使用 Python sqlite3)

使用 python 中的 hypopt 包在 GridSearch 函数中指定评分指标

Python Sqlite3 executemany 中的绑定数量不正确

如何使用循环打印 sqlite3 中的表以及 python 中的列名以及如何准确获取列名?

在 Spark 中的 EMR 上使用 --py-files 从 .zip 文件(使用 zipfile 包在 python 中创建)导入模块时出现问题

在 python 中使用 scikit 包在 SVM 中获取负 alpha 值