我最近在编写一个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:
            ...