【发布时间】: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,这应该会有所帮助。我认为您可能只是没有干净地使用事务。