【问题标题】:peewee mutithreading "database is locked"peewee 多线程“数据库已锁定”
【发布时间】:2022-01-16 00:19:54
【问题描述】:

我正在开发在多个线程中工作并并行执行数据库操作的应用程序。虽然通常它运行良好,但有时我会遇到数据库繁忙的异常。我应该说创建一个紧凑的可重现示例是一项相当复杂的任务,但不知何故我最终得到了这个:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep
from random import random
from peewee import SqliteDatabase, Model, FloatField

db = SqliteDatabase("test.db", pragmas={"journal_mode": "wal"}, timeout=10)

class TestModel(Model):
    number = FloatField()

    class Meta:
        database = db

def func(number):
    with db.connection_context():
        with db.atomic():
            TestModel.create(number=number)
            sleep(number)

db.create_tables([TestModel])

with ThreadPoolExecutor() as executor:
    futures = [executor.submit(func, random()) for _ in range(100)]

    for future in as_completed(futures):
        result = future.result()

这段代码抛出异常:

Traceback (most recent call last):
  File "\venv\lib\site-packages\peewee.py", line 3160, in execute_sql
    cursor.execute(sql, params or ())
sqlite3.OperationalError: database is locked

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "\test.py", line 33, in <module>
    print(future.result())
  File "\Python39\lib\concurrent\futures\_base.py", line 438, in result
    return self.__get_result()
  File "\Python39\lib\concurrent\futures\_base.py", line 390, in __get_result
    raise self._exception
  File "\Python39\lib\concurrent\futures\thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "\test.py", line 19, in func
    TestModel.create(number=number)
  File "\venv\lib\site-packages\peewee.py", line 6393, in create
    inst.save(force_insert=True)
  File "\venv\lib\site-packages\peewee.py", line 6603, in save
    pk = self.insert(**field_dict).execute()
  File "\venv\lib\site-packages\peewee.py", line 1911, in inner
    return method(self, database, *args, **kwargs)
  File "\venv\lib\site-packages\peewee.py", line 1982, in execute
    return self._execute(database)
  File "\venv\lib\site-packages\peewee.py", line 2761, in _execute
    return super(Insert, self)._execute(database)
  File "\venv\lib\site-packages\peewee.py", line 2479, in _execute
    cursor = database.execute(self)
  File "\venv\lib\site-packages\peewee.py", line 3173, in execute
    return self.execute_sql(sql, params, commit=commit)
  File "\venv\lib\site-packages\peewee.py", line 3167, in execute_sql
    self.commit()
  File "\venv\lib\site-packages\peewee.py", line 2933, in __exit__
    reraise(new_type, new_type(exc_value, *exc_args), traceback)
  File "\venv\lib\site-packages\peewee.py", line 191, in reraise
    raise value.with_traceback(tb)
  File "\venv\lib\site-packages\peewee.py", line 3160, in execute_sql
    cursor.execute(sql, params or ())
peewee.OperationalError: database is locked

我已将sleep() 调用添加到db.atomic() 上下文中,只是为了模拟一些需要数百毫秒的复杂数据库操作。

我知道 SQLite 在一段时间内允许单个写入器,因此我将所有写入操作放入 db.atomic(),但由于某些原因,此上下文中的代码会引发数据库繁忙的异常。

我做错了什么?


我知道从技术上讲为什么会发生这种情况。据我了解,当线程执行.atomic() 调用并且其他一些线程正在为事务保存数据库时,它会等到超时并抛出异常。问题是..为什么?休眠不到一秒,超时设置为10秒,所以应该有足够的时间等到数据库被释放并跳转进去。在实际应用事务中甚至不需要100毫秒,但偶尔会发生这种异常。


此异常在代码中的任何原子更新时随机发生。我什至添加了日志记录来检查我的任何事务最多花费多少时间,最长为 81 毫秒,超时设置为 20 秒,每秒最多发生 7 次事务,所以我不知道它为什么会在内部死亡。切换数据库引擎或切换到低级sqlite3 可能会更容易,但我无意再与此作斗争。

【问题讨论】:

  • WAL 模式的故障模式与常规日志模式不同。
  • @coleifer,没关系。我已经尝试过删除所有的编译指示,而且都是一样的。在真正的应用程序中,作为基于网络请求的工作流,这是不可能追踪的。一旦它创建一个模型,其他时间完全更新另一个模型。
  • @coleifer,是的,谢谢你的链接,我已经在谷歌找到了。不幸的是,SqliteQueueDatabase 在我的特殊情况下无法完全防止异常。很少有机会执行第二个写入器进程。它执行单个事务,但会锁定可能导致相同异常的数据库。经过一些测试和修复尝试后,我得出结论,SQLite 在这里不是一个合适的数据库。虽然项目看起来很小,但并发写入将 SQLite 变成了一个问题。我切换到 PostgreSQL,通过一些查询优化,它展示了下降性能。
  • 阅读事务部分:charlesleifer.com/blog/going-fast-with-sqlite-and-python——如果你决定使用 sqlite,这应该会有所帮助。我认为您可能只是没有干净地使用事务。

标签: python sqlite peewee


【解决方案1】:

SQLite 本质上是一个单线程数据库。 要允许多线程,您应该使用特定参数初始化 sqlite 线程真实。 在这里阅读更多:https://www.sqlite.org/threadsafe.html 你可以通过设置 check_same_thread=False 来做到这一点

【讨论】:

  • 你可能没有注意到我使用的是peewee ORM,而不是标准的sqlite模块。我确信它在内部管理所有必要的标志和参数。
  • 它只是一个使用 sqlite 模块的后端,它可以工作。在编译指示中传递 check_same_thread=False。
  • 当然它在内部使用 sqlite3,但我确信多线程所需的所有设置都已完成。构造函数中有thread_safe参数,默认为True
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-27
  • 1970-01-01
相关资源
最近更新 更多