Sqlalchemy:使用装饰器为多个函数提供线程安全的会话

7 投票
2 回答
9036 浏览
提问于 2025-04-18 16:12

我正在做一个类似于这里的项目(使用sqlalchemy的多线程应用),所以我明白每次数据库查询都应该创建一个新的会话。

我在想,给每个需要访问数据库的方法加上一个装饰器是否合理,或者这样做会不会有什么陷阱。这个装饰器的构造是参考了这里的最后一个例子

def dbconnect(func):
    def inner(*args, **kwargs):
        session = Session()  # with all the requirements
        try:
            func(*args, session=session, **kwargs)
            session.commit()
        except:
            session.rollback()
            raise
        finally:
            session.close()
    return inner

@dbconnect
def some_function(some, arguments, session)
    session.query(...) # no commit, close, rollback required

some_function("many", "different_arguments") 
#session is not required, since provided by decorator

这样做的话,就可以比较简单地为任何函数提供线程安全的数据库访问,而不需要重复实现整个try-except-finally的流程,但我不确定这种方法是否安全可靠,也不清楚是否有更好的实践方法。

2 个回答

7

我觉得在这里使用一个叫做 scoped_session 的东西是有道理的,可能可以这样做:

session_factory = sessionmaker(bind=some_engine)
Session = scoped_session(session_factory)

def dbconnect(func):
    def inner(*args, **kwargs):
        session = Session()  # (this is now a scoped session)
        try:
            func(*args, **kwargs) # No need to pass session explicitly
            session.commit()
        except:
            session.rollback()
            raise
        finally:
            Session.remove()  # NOTE: *remove* rather than *close* here
    return inner

@dbconnect
def some_function(some, arguments):
    session = Session()
    # 'session' is  now a handle to the *same* session as in the decorator
    session.query(...) # no commit, close, rollback required

some_function("many", "different_arguments") 
#session is not required, since provided by decorator

(警告:这个代码没有经过测试)

2

带有参数的装饰器很有意思,但可能会有点棘手。因为定义的参数列表和实际调用时使用的参数不一致。如果你明确地传入 session=something,就会出现错误(不过你可以在装饰器里检查这个情况)。

你还需要至少添加一个 functools.wraps(当然,这只是个简短的示例代码)。

事务是使用上下文管理器的一个好例子。想了解更多,可以看看这个链接:在多线程的sqlalchemy网页应用中,推荐的scoped_session使用模式是什么?

撰写回答