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

Multiple write single read SQLite application with Peewee

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

设置

我正在使用 peewee 记录机器学习实验。基本上,我有 多个节点 (比如,不同的计算机),其中 运行 一个 python 文件,所有 write 到共享位置中的 same base.db 文件。最重要的是,我需要从我的笔记本电脑进行一次 read 访问,以查看发生了什么。最多有大约 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,因为根据 documentation,如果“如果你想从多个线程对 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 对象从多台机器初始化的事实不同于在一台机器上有一个对象多线程 ()。正确的?那有什么好的处理方法吗?

抱歉,如果这个问题已经在其他地方得到解答,感谢您的帮助!当然,如果需要,很乐意提供更多代码。

确实,在@BoarGules 发表评论后,我意识到我混淆了两个截然不同的事情:

  • 在一台机器上有多个线程:在这里,SqliteQueueDatabase 非常适合
  • 拥有多台机器,一个或多个线程:互联网基本上就是这样工作的。

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

  • 安装 Postgre。如果您没有 root 权限,您可以从源代码构建它,遵循官方文档中的 chapter 17,然后是第 19 章。
  • 您可以使用 pgloader 导出 SQLite 数据库。但同样,如果您没有合适的库并且不想构建所有内容,您可以手动完成。我做了以下操作,不确定是否存在更直接的解决方案。
  1. 将您的 table 导出为 csv(在 之后):
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 数据库中创建 table:
db = pw.PostgresqlDatabase('mydb', host='localhost')
db.create_tables([Experiment, Epoch], safe=True)
  1. 使用以下命令将 CSV tables 复制到 Postgre 数据库:
COPY epoch("col1", "col2", ...) FROM '/absolute/path/to/epoch.csv'; DELIMITER ',' CSV;

其他 table 也是如此。

它对我来说很好用,因为我只有两个 table。如果您拥有的不止于此,可能会很烦人。 pgloader 在这种情况下似乎是一个很好的解决方案,如果您可以轻松安装它的话。

更新

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

ALTER SEQUENCE epoch_id_seq RESTART WITH 10000

并用 SELECT MAX("id") FROM epoch+1.

中的值替换 10000

Sqlite 一次只支持一个写入器,但是在使用 WAL-mode 时,多个读取器可以打开数据库(即使连接了一个写入器)。对于 peewee,您可以启用 wal 模式:

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

另一件重要的事情是,当使用多个写入器时,让您的写入事务尽可能短。可以在此处找到一些建议:https://charlesleifer.com/blog/going-fast-with-sqlite-and-python/ 在“事务、并发和自动提交”标题下。

另请注意,SqliteQueueDatabase 适用于具有 多个 线程的 单个 进程,但如果您有多个线程,则完全无济于事进程.