如何使Flask SQLAlchemy重用数据库连接?

Tom*_*ham 21 postgresql sqlalchemy flask flask-sqlalchemy

我似乎无法让我的Flask应用程序关闭或重用数据库连接.我正在使用PostgreSQL 9.1.3和

Flask==0.8
Flask-SQLAlchemy==0.16
psycopg2==2.4.5
Run Code Online (Sandbox Code Playgroud)

当我的测试套件运行时,打开的连接数量会攀升,直到达到20(max_connections设置为止postgresql.conf),然后我看到:

OperationalError: (OperationalError) FATAL:  sorry, too many clients already
 None None
Run Code Online (Sandbox Code Playgroud)

我已经减少了代码,它只是调用点create_alldrop_all(但不发出任何SQL,因为没有型号).

我在日志中看到要检入的连接:

DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool
WARNING:root:impl   <-------- That's the test running
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool
Run Code Online (Sandbox Code Playgroud)

对于每次测试运行,连接的地址("xyz"部分的"连接对象")是不同的.我怀疑这与问题有关,但我不确定如何进一步调查.

下面的代码在新的venv中重现了这个问题:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from unittest import TestCase

import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.dialects').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.orm').setLevel(logging.DEBUG)


db = SQLAlchemy()

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_object(config)
    db.init_app(app)
    return app


class AppTestCase(TestCase):
    SQLALCHEMY_DATABASE_URI = "postgresql://localhost/cx_test"
    TESTING = True

    def create_app(self):
        return create_app(self)

    def setUp(self):
        self.app = self.create_app()
        self.client = self.app.test_client()
        self._ctx = self.app.test_request_context()
        self._ctx.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self._ctx.pop()


class TestModel(AppTestCase):
    def impl(self):
        logging.warn("impl")
        pass

    def test_01(self):
        self.impl()

    def test_02(self):
        self.impl()

    def test_03(self):
        self.impl()

    def test_04(self):
        self.impl()

    def test_05(self):
        self.impl()

    def test_06(self):
        self.impl()

    def test_07(self):
        self.impl()

    def test_08(self):
        self.impl()

    def test_09(self):
        self.impl()

    def test_10(self):
        self.impl()

    def test_11(self):
        self.impl()

    def test_12(self):
        self.impl()

    def test_13(self):
        self.impl()

    def test_14(self):
        self.impl()

    def test_15(self):
        self.impl()

    def test_16(self):
        self.impl()

    def test_17(self):
        self.impl()

    def test_18(self):
        self.impl()

    def test_19(self):
        self.impl()



if __name__ == "__main__":
    import unittest
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

这是我第一次在烧瓶中使用app工厂,我从Flask-SQLAlchemy文档中部分复制了这段代码.Elseware那些文档提到在错误的上下文中使用db会导致连接泄漏 - 也许我正在做错的init?

C. *_*Lee 11

在阅读了SQLAlchemy文档和一些摆弄数据库实例之后,我终于得到了解决方案.添加db.get_engine(self.app).dispose()tearDown()这样它看起来像:

def tearDown(self):
    db.session.remove()
    db.drop_all()
    db.get_engine(self.app).dispose()
    self._ctx.pop()
Run Code Online (Sandbox Code Playgroud)


Ale*_*hen 8

自从大约一年前提出问题以来,我认为OP必须解决他的问题.但对于那些徘徊在这里的人(像我一样)试图弄清楚到底发生了什么,这是我最好的解释:

正如van所说,问题确实在于测试用例调用setUptearDown每次测试.虽然连接并没有完全从SQLAlchemy泄漏,但是由于每个测试都有自己的连接setUp,所以创建了应用程序的多个实例:每个应用程序都有自己的数据库连接池,大概不会重用或回收测试结束.

换句话说,正在检出连接并将其正确地返回到池中,但是连接然后作为空闲连接而存在,以用于同一应用程序中的未来事务(连接池的连接点).

在上面的测试用例中,创建了大约20个连接池(每个连接池由于create/drop_all而具有空闲连接)并占用postgres连接限制.