首页 > 解决方案 > 带有 QueuePool 和两个进程的 SQLAlchemy:结帐时关闭共享连接可以吗?

问题描述

我正在使用烧瓶、flask_SQLAlchemy 和 postgres。两个单线程进程正在为我的开发应用程序提供服务。起初我有以下代码片段:

app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'pool_size': 1, 'pool_pre_ping': True, 'pool_recycle': 280}
db = SQLAlchemy(app)
db.create_all()

这个在测试我的 API 时产生了 OperationalError。我找到了三个参考文献 [1] [2] [3] 来解释这个问题。他们指出,在分叉之后,两个进程共享源自 create_all() 的相同连接,这恰好是问题的原因。因此,我利用已经提供的解决方案之一在结帐时从池中删除共享连接:

def add_engine_pidguard(engine):
    """Add multiprocessing guards.

    Forces a connection to be reconnected if it is detected
    as having been shared to a sub-process.

    """

    @event.listens_for(engine, "connect")
    def connect(dbapi_connection, connection_record):
        connection_record.info['pid'] = os.getpid()

    @event.listens_for(engine, "checkout")
    def checkout(dbapi_connection, connection_record, connection_proxy):
        pid = os.getpid()
        if connection_record.info['pid'] != pid:
            warnings.warn(
                "Parent process %(orig)s forked (%(newproc)s) with an open "
                "database connection, "
                "which is being discarded and recreated." %
                {"newproc": pid, "orig": connection_record.info['pid']})
            connection_record.connection = connection_proxy.connection = None

            raise exc.DisconnectionError(
                "Connection record belongs to pid %s, "
                "attempting to check out in pid %s" %
                (connection_record.info['pid'], pid)
            )

并且add_engine_pidguard(db.engine)紧随其后db = SQLAlchemy(app)。这种方法终于摆脱了错误,但是

SELECT *
    FROM pg_stat_activity
    WHERE query NOT LIKE '%%FROM pg_stat_activity%%';

表明,虽然第一个连接被从池中丢弃,但仍保留在数据库中。所以我最终得到了三个连接。我觉得根据先前 SQL 语句的“查询”字段,第一个连接似乎被用于 pre_pring 有点可怕。为了防止多个进程再次共享同一个连接,我添加了:

if not dbapi_connection.closed:
    dbapi_connection.close()

在我提出 DisconnectionError 之前def checkout(dbapi_connection, connection_record, connection_proxy):

之后,我的数据库中只出现了两个连接,到目前为止没有发生任何错误。

现在请问,这样做安全吗?我担心这两个进程可能会同时尝试关闭连接。

编辑:我知道使用 NullPool 而不是 QueuePool 可以解决这样的问题。但是,NullPool 会导致成本高昂的操作,例如为每个请求创建和关闭数据库连接,我不太确定在有很多请求的情况下这会有多糟糕。

[1] https://davidcaron.dev/sqlalchemy-multiple-threads-and-processes/

[2] https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing

[3] https://docs-sqlalchemy.readthedocs.io/ko/latest/faq/connections.html

标签: pythonpostgresqlflasksqlalchemymultiprocessing

解决方案


推荐阅读