Python Peewee、SQLite和线程

Python Peewee、SQLite和线程,python,multithreading,sqlite,peewee,Python,Multithreading,Sqlite,Peewee,我正在处理一个线程应用程序,其中一个线程将向队列提供要修改的对象,然后许多其他线程将从队列中读取、执行修改并保存更改 应用程序不需要很多并发性,所以我希望使用SQLite数据库。下面是一个小示例,演示了该应用程序: import queue import threading import peewee as pw db = pw.SqliteDatabase('test.db', threadlocals=True) class Container(pw.Model): conten

我正在处理一个线程应用程序,其中一个线程将向
队列
提供要修改的对象,然后许多其他线程将从队列中读取、执行修改并保存更改

应用程序不需要很多并发性,所以我希望使用SQLite数据库。下面是一个小示例,演示了该应用程序:

import queue
import threading
import peewee as pw

db = pw.SqliteDatabase('test.db', threadlocals=True)

class Container(pw.Model):
    contents = pw.CharField(default="spam")

    class Meta:
        database = db


class FeederThread(threading.Thread):

    def __init__(self, input_queue):
        super().__init__()

        self.q = input_queue

    def run(self):
        containers = Container.select()

        for container in containers:
            self.q.put(container)


class ReaderThread(threading.Thread):

    def __init__(self, input_queue):
        super().__init__()

        self.q = input_queue

    def run(self):
        while True:
            item = self.q.get()

            with db.execution_context() as ctx:
                # Get a new connection to the container object:
                container = Container.get(id=item.id)
                container.contents = "eggs"
                container.save()

            self.q.task_done()


if __name__ == "__main__":

    db.connect()
    try:
        db.create_tables([Container,])
    except pw.OperationalError:
        pass
    else:
        [Container.create() for c in range(42)]
    db.close()

    q = queue.Queue(maxsize=10)


    feeder = FeederThread(q)
    feeder.setDaemon(True)
    feeder.start()

    for i in range(10):
        reader = ReaderThread(q)
        reader.setDaemon(True)
        reader.start()

    q.join()
基于peewee文档,SQLite应该支持多线程。但是,我一直收到臭名昭著的
peewee.OperationalError:数据库被锁定
错误,错误输出指向
container.save()


我该怎么解决这个问题呢?

你试过WAL模式吗

如果您同时访问SQLite,您必须非常小心,因为在写操作完成时,整个数据库都会被锁定,尽管可以有多个读卡器,但写操作将被锁定。通过在较新的SQLite版本中添加WAL,这一点有所改进

如果使用多个线程,可以尝试使用共享页面缓存,这将允许在线程之间共享加载的页面,从而避免昂贵的I/O调用


你试过沃尔模式吗

如果您同时访问SQLite,您必须非常小心,因为在写操作完成时,整个数据库都会被锁定,尽管可以有多个读卡器,但写操作将被锁定。通过在较新的SQLite版本中添加WAL,这一点有所改进

如果使用多个线程,可以尝试使用共享页面缓存,这将允许在线程之间共享加载的页面,从而避免昂贵的I/O调用


我有点惊讶地看到这个失败,所以我复制了你的代码,并尝试了一些不同的想法。我认为问题在于,默认情况下,
ExecutionContext()
将导致包装的块在事务中运行。为了避免这种情况,我在阅读器线程中传入了
False

我还编辑了feeder,以便在将内容放入队列之前使用SELECT语句(
list(Container.SELECT())

以下是我在当地的工作:

class FeederThread(threading.Thread):

    def __init__(self, input_queue):
        super(FeederThread, self).__init__()

        self.q = input_queue

    def run(self):
        containers = list(Container.select())

        for container in containers:
            self.q.put(container.id)  # I don't like passing model instances around like this, personal preference though

class ReaderThread(threading.Thread):

    def __init__(self, input_queue):
        super(ReaderThread, self).__init__()

        self.q = input_queue

    def run(self):
        while True:
            item = self.q.get()

            with db.execution_context(False):
                # Get a new connection to the container object:
                container = Container.get(id=item)
                container.contents = "nuggets"
                with db.atomic():
                    container.save()

            self.q.task_done()

if __name__ == "__main__":

    with db.execution_context():
        try:
            db.create_tables([Container,])
        except OperationalError:
            pass
        else:
            [Container.create() for c in range(42)]

    # ... same ...
我对此并不完全满意,但希望它能给你一些想法


这是我不久前写的一篇博文,其中有一些提高SQLite并发性的技巧:

我很惊讶地看到这一点也失败了,所以我复制了您的代码并尝试了一些不同的想法。我认为问题在于,默认情况下,
ExecutionContext()
将导致包装的块在事务中运行。为了避免这种情况,我在阅读器线程中传入了
False

我还编辑了feeder,以便在将内容放入队列之前使用SELECT语句(
list(Container.SELECT())

以下是我在当地的工作:

class FeederThread(threading.Thread):

    def __init__(self, input_queue):
        super(FeederThread, self).__init__()

        self.q = input_queue

    def run(self):
        containers = list(Container.select())

        for container in containers:
            self.q.put(container.id)  # I don't like passing model instances around like this, personal preference though

class ReaderThread(threading.Thread):

    def __init__(self, input_queue):
        super(ReaderThread, self).__init__()

        self.q = input_queue

    def run(self):
        while True:
            item = self.q.get()

            with db.execution_context(False):
                # Get a new connection to the container object:
                container = Container.get(id=item)
                container.contents = "nuggets"
                with db.atomic():
                    container.save()

            self.q.task_done()

if __name__ == "__main__":

    with db.execution_context():
        try:
            db.create_tables([Container,])
        except OperationalError:
            pass
        else:
            [Container.create() for c in range(42)]

    # ... same ...
我对此并不完全满意,但希望它能给你一些想法


这是我不久前写的一篇博文,其中有一些提高SQLite并发性的技巧:

谢谢你的建议!不幸的是,WAL和使用共享缓存似乎都没有什么不同。谢谢你的建议!不幸的是,WAL和使用共享缓存似乎都没有什么不同。谢谢你的回答!穿线对我来说仍然有点像巫术,所以我很高兴我没有犯明显的错误。有趣的是,使用SELECT语句似乎是这里的关键——使用
db.execution\u context(False)
与db.atomic()
没有任何区别。事实上,通过使用SELECT语句,我甚至不需要
ExecutionContext()
。所以我想SELECT语句实际上是在锁定数据库?谢谢你的回答!穿线对我来说仍然有点像巫术,所以我很高兴我没有犯明显的错误。有趣的是,使用SELECT语句似乎是这里的关键——使用
db.execution\u context(False)
与db.atomic()
没有任何区别。事实上,通过使用SELECT语句,我甚至不需要
ExecutionContext()
。我想SELECT语句实际上是在锁定数据库?