SQLAlchemy/Python:每个进程使用一个引擎,并将该引擎注入依赖项

2 投票
1 回答
1176 浏览
提问于 2025-04-18 17:35

SQLAlchemy建议每个进程使用一个数据库引擎,因为底层的数据库连接在不同进程之间可能不安全。

我有全局的enginesession变量,它们会在需要的时候被赋值为SQLAlchemy的引擎和会话工厂。这样一来,编写需要数据库的辅助函数时,就可以在运行时调用get_session()来使用已经通过init_db赋值的session变量。

这种方式很好,因为它允许需要访问enginesession的辅助函数在这两个变量初始化之前就被导入。但这个模式让我只能在全局范围内使用一个enginesession

然而,我真正需要做的是:

engine, session = init_db(DB_URL)

...从调用的应用程序或方法中,然后将会话注入到这些数据库辅助函数里。为什么呢?因为我需要每个进程使用一个引擎。我觉得这里需要一种控制反转的模式,但我在想出一个可行的方案时遇到了困难(不想在每次调用时把会话传递给辅助函数,这样做显得很笨拙,是最后的选择)。

我原以为可以在os.fork()之后设置引擎。每个进程开始时engine=None,然后再实例化它。但engine在父进程和子进程中有相同的内存地址。这是有道理的;这就是Python的工作方式。

from utils import init_db, engine, DB_URL
import os

def connect():
    print('before fork, engine is: {}'.format(id(engine)))
    newpid = os.fork()

    if newpid == 0:
        init_db(DB_URL)
        print('child engine is: {}'.format(id(engine))) # id(engine) is equal to below call
    else:
        init_db(DB_URL)
        print('parent engine is: {}'.format(id(engine))) # id(engine) is equal to above call

if __name__ == '__main__':
    connect()

我最好的选择是根据进程ID创建一个连接池吗?如果可能的话,我希望使用一种干净的控制反转模式。

NullPool不进行连接池管理,这样可以解决问题,但我觉得这并不是一个好主意——因为不断地打开和关闭连接似乎很耗费资源?

Python版本:3.4.0

SQLAlchemy版本:0.9.7

1 个回答

1

id() 的结果可能会让人误解,因为它和虚拟内存有关。在你使用 fork() 这个命令后,Python 对象会独立运行,彼此之间不会共享状态的修改。问题在于,fork() 之前打开的文件、管道和套接字会被共享,这就是为什么如果你在使用连接池时需要使用不同的引擎。

如果你不清楚 init_db 具体做了什么,但如果在 fork 之后创建引擎,那就没问题了——每个进程都会有自己打开的套接字。为了更清楚你的意图,可能把 import 移到 fork 之后会更好。

撰写回答