使用Peewee的多写单读SQLite应用程序

mbzjlibv  于 2022-11-24  发布在  SQLite
关注(0)|答案(3)|浏览(211)

我在多台机器上使用一个带有peewee的SQLite数据库,我遇到了各种OperationalErrorDataBaseError。这显然是多线程的问题,但我根本不是这方面的Maven,也不是SQL方面的Maven。下面是我的设置和我所尝试的。

设置

我使用peewee记录机器学习实验。基本上,我有多个节点(比如,不同的电脑)运行一个python文件,并且都到一个共享位置的**base.db文件。最重要的是,我需要一个从我的笔记本电脑的访问权限,最多有50个不同的节点示例化数据库并在其上写入内容。
我所尝试的
首先,我使用SQLite对象:

db = pw.SqliteDatabase(None)

# ... Define tables Experiment and Epoch

def init_db(file_name: str):
    db.init(file_name)
    db.create_tables([Experiment, Epoch], safe=True)
    db.close()

def train():
    xp = Experiment.create(...)

    # Do stuff
    with db.atomic():  
        Epoch.bulk_create(...)
    xp.save()

这工作得很好,但我有时会有作业因为数据库被锁定而崩溃。然后,我了解到SQLite只处理每个连接的一个写操作,这就导致了问题。
因此,我转向SqliteQueueDatabase,因为根据文档,如果“如果您希望从多个线程对SQLite数据库进行简单的读写访问”,它是有用的。
代码如下所示:

db = SqliteQueueDatabase(None, autostart=False, pragmas=[('journal_mode', 'wal')],
                         use_gevent=False,)

def init_db(file_name: str):
    db.init(file_name)
    db.start()
    db.create_tables([Experiment, Epoch], safe=True)
    db.connect()

除了db.atomic部分之外,保存数据也是一样的。但是,不仅写查询似乎遇到了错误,而且我实际上不再能够访问数据库进行读操作:它几乎总是忙碌的。
我的问题
在这种情况下使用什么对象是合适的?我认为SqliteQueueDatabase是最合适的。pooled数据库是更合适的吗?我问这个问题也是因为我不知道我是否很好地掌握了线程部分:从多台机器初始化多个database对象与在一台机器上使用多个线程(like this situation)初始化一个对象是不同的。
很抱歉,如果这个问题已经在另一个地方回答,并感谢任何帮助!高兴地提供更多的代码,如果需要当然。

u5i3ibmn

u5i3ibmn1#

Sqlite一次只支持一个writer,但是当使用WAL模式时,多个reader可以打开数据库(即使writer已经连接)。对于peewee,您可以启用wal模式:

db = SqliteDatabase('/path/to/db', pragmas={'journal_mode': 'wal'})

使用多个写入器时,另一个重要的事情是使写入事务尽可能短。https://charlesleifer.com/blog/going-fast-with-sqlite-and-python/(位于“事务、并发和自动提交”标题下)。
还请注意,SqliteQueueDatabase对于 * 单个 * 进程和 * 多个 * 线程运行良好,但如果您有多个进程,它将对您毫无帮助。

a14dhokn

a14dhokn2#

在@BoarGules评论之后,我意识到我混淆了两件截然不同的事情:

  • 在一台机器上有多个线程:在这里,SqliteQueueDatabase是一个非常合适值
  • 多线程的:有多台机器的,有一个或多个线程的:这就是互联网基本运作方式

所以我最终安装了Postgre。如果它对我之后的人有用的话,有几个链接,对于linux

  • 安装Postgre。如果你没有root权限,你可以从源代码构建它,从官方文档chapter 17开始,然后是第19章。
  • 你可以用pgloader导出SQLite数据库。但是,如果你没有合适的库,也不想构建所有的东西,你可以手工完成。我做了下面的事情,不确定是否有更直接的解决方案。

1.将您的表导出为csv(遵循@coleifer的注解):

models = [Experiment, Epoch]
for model in models:
    outfile = '%s.csv' % model._meta.table_name
    with open(outfile, 'w', newline='') as f:
        writer = csv.writer(f)
        row_iter = model.select().tuples().iterator()
        writer.writerows(row_iter)

1.在新的Postgre数据库中创建表:

db = pw.PostgresqlDatabase('mydb', host='localhost')
db.create_tables([Experiment, Epoch], safe=True)

1.使用以下命令将CSV表复制到Postgre db:

COPY epoch("col1", "col2", ...) FROM '/absolute/path/to/epoch.csv'; DELIMITER ',' CSV;

其它表也是如此。
它对我来说很好用,因为我只有两张表。如果你有更多的表,可能会很烦人。在这种情况下,如果你能很容易地安装它,pgloader似乎是一个非常好的解决方案。

更新

一开始我无法从peewee创建对象。出现完整性错误:似乎Postgre返回的id(使用RETURNING 'epoch'.'id'子句)返回一个已经存在的id。据我所知,这是因为在使用COPY命令时没有调用增量。因此,它只返回id 1,然后是2,依此类推,直到它到达一个不存在的id。为了避免经历所有这些失败的创建,您可以直接编辑控制RETURN子句的迭代器,使用:

ALTER SEQUENCE epoch_id_seq RESTART WITH 10000

并将10000替换为SELECT MAX("id") FROM epoch+1中的值。

k4aesqcs

k4aesqcs3#

我认为您可以增加sqlite的超时时间并解决您的问题

这里的问题是sqlite默认的写操作超时时间很低,即使有少量的并发写操作,sqlite也会抛出异常,这是很常见的,也是众所周知的。
默认值应该是5-10秒。如果超过了这个超时时间,那么要么增加它,要么将你的数据写入数据库。
以下是一个示例:我在这里返回一个DatabaseProxy,因为这个代理允许在不更改客户端代码的情况下将sqlite替换为postgres。

import atexit
from peewee import DatabaseProxy  # type: ignore
from playhouse.db_url import connect  # type: ignore
from playhouse.sqlite_ext import SqliteExtDatabase  # type: ignore

DB_TIMEOUT = 5

def create_db(db_path: str) -> DatabaseProxy:
    pragmas = (
        # Negative size is per api spec.
        ("cache_size", -1024 * 64),
        # wal speeds up writes.
        ("journal_mode", "wal"),
        ("foreign_keys", 1),
    )
    sqlite_db = SqliteExtDatabase(
        db_path,
        timeout=DB_TIMEOUT,
        pragmas=pragmas)
    sqlite_db.connect()
    atexit.register(sqlite_db.close)
    db_proxy: DatabaseProxy = DatabaseProxy()
    db_proxy.initialize(sqlite_db)
    return db_proxy

相关问题