使用 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=memory
和cache=shared
。
@compostus,看看我对这个问题的新回答。以上是关于使用 sqlite3 包在 python 中的不同线程之间共享一个:memory: 数据库的主要内容,如果未能解决你的问题,请参考以下文章
使用 `executemany` 更新现有 SQLite3 数据库中的条目(使用 Python sqlite3)
使用 python 中的 hypopt 包在 GridSearch 函数中指定评分指标
Python Sqlite3 executemany 中的绑定数量不正确
如何使用循环打印 sqlite3 中的表以及 python 中的列名以及如何准确获取列名?
在 Spark 中的 EMR 上使用 --py-files 从 .zip 文件(使用 zipfile 包在 python 中创建)导入模块时出现问题