我最近在编写一个fastapi的后端项目,前端在使用的时候经常会出现500的错误,在查看日志后发现是数据库的问题
pymysql.err.InternalError: Packet sequence number wrong - got 1 expected 2
这是一个并发导致的数据库处理错误,下面是数据库处理的代码
class DataBaseProcess:
'''自动数据库处理'''
def __init__(self, connecter: str):
self.engine = create_engine(connecter)
if TEST:
Base.metadata.create_all(self.engine) # type: ignore # 自动创建表结构
self.DBsession = sessionmaker(bind=self.engine)
def __enter__(self):
self.session = self.DBsession()
return self.session
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
self.session.rollback()
else:
self.session.commit()
self.session.close()
逻辑很简单,使用python的上下文管理来调用这个实例,在进入的时候实例化一个session
并返回,在退出的时候关闭session
。
在业务逻辑中我是这么使用的
class Processer:
def __init__(self) -> None:
self.database = DataBaseProcess(SQL_CONNECTER)
...
def __get_editor_data(self, update_editor: str) -> EditorActivatily|None:
'''
获取活跃作者数据库数据
Parameters:
update_editor: 作者名
Returns:
- 若存在返回EditorActivatily
- 若不存在返回None
'''
with self.database as session:
...
发现问题了吗,我将DataBaseProcess
实例化后存储了起来,每次调用的都是同一个实例,然而在DataBaseProcess
中每次进入上下文时都会重新实例化一个session
,而为了在退出时释放session
我将其保存在了self.session
没错,如果是都是同步处理的情况下确实没有什么问题,但是fastapi会并发的处理所有的请求,这就导致了上一个session
还未处理完新的session
就被覆盖到了self.session
中,而上一个在退出时就会错误的释放新的session
。
这其实是挺低级的错误,因为我在最初编写时保持着尽量减少实例化的操作来优化代码,然后因为疏忽就导致了这个问题,解决起来也非常简单只需要在每次调用时都重新实例化就行,当然也可以写一个队列来处理,但是这样会导致响应速度的下降。
上面的业务逻辑就可以这么修改
class Processer:
def __init__(self) -> None:
...
def __get_editor_data(self, update_editor: str) -> EditorActivatily|None:
'''
获取活跃作者数据库数据
Parameters:
update_editor: 作者名
Returns:
- 若存在返回EditorActivatily
- 若不存在返回None
'''
with DataBaseProcess(SQL_CONNECTER) as session:
...