我正在开发一个在多个线程中工作并并行执行数据库操作的应用程序。虽然通常它运行良好,但有时我会遇到数据库繁忙的异常。我应该说创建一个紧凑的可重复示例是相当复杂的任务,但不知何故我最终得到了这个:
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
,但我不想再为此奋斗了。
SQLite 本质上是一个单线程数据库。 要允许多线程,您应该使用特定参数初始化 sqlite 螺纹真实。 在这里阅读更多信息:https://www.sqlite.org/threadsafe.html 你可以通过设置 check_same_thread=False 来做到这一点
我发现我在关闭数据库连接之前通过打开数据库进行了备份,导致了我这个问题。 在所有数据库都获得 .close() 后,我将备份移至 python 的末尾
“with open(file, 'rb') as f:” - 锁定文件 1/1000 次。