You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Django测试会打开大量数据库连接?

解决Schemathesis + Django测试中数据库连接耗尽问题

问题重现

测试代码如下:

from contextlib import contextmanager

import schemathesis
from hypothesis import given, settings
from hypothesis.extra.django import LiveServerTestCase as HypothesisLiveServerTestCase

from my_app.api import api_v1
from my_app.tests import AuthTokenFactory

# 3 requests per second - `3/s`
# 100 requests per minute - `100/m`
# 1000 requests per hour - `1000/h`
# 10000 requests per day - `10000/d`
RATE_LIMIT = "10/s"

openapi_schema = api_v1.get_openapi_schema()


@contextmanager
def authenticated_strategy(strategy, token: str):
    @given(case=strategy)
    @settings(deadline=None)
    def f(case):
        case.call_and_validate(headers={"Authorization": f"Bearer {token}"})

    yield f


class TestApiSchemathesis(HypothesisLiveServerTestCase):
    """
    Tests the REST API using Schemathesis.

    It does this by spinning up a live server, and then uses Schemathesis to
    automatically generate schema-conforming requests to the API.
    """

    def setUp(self):
        super().setUp()
        self.schema = schemathesis.from_dict(
            openapi_schema, base_url=self.live_server_url, rate_limit=RATE_LIMIT
        )

    def test_api(self):
        """
        Loop over all API endpoints and methods, and runs property tests for each.
        """
        auth_token = AuthTokenFactory().token
        for endpoint in self.schema:
            for method in self.schema[endpoint]:
                with self.subTest(endpoint=f"{method.upper()} {endpoint}"):
                    strategy = self.schema[endpoint][method].as_strategy()
                    with authenticated_strategy(strategy, auth_token) as run_strategy:
                        run_strategy()

新增端点后出现数据库连接耗尽错误:

psycopg2.OperationalError: connection to server at "localhost" (::1), port 5432 failed: FATAL: sorry, too many clients already

django.db.utils.OperationalError: database "test_myapp" is being accessed by other users
DETAIL: There are 99 other sessions using the database.

可能原因

  • Hypothesis默认多线程生成测试用例,每个请求新建数据库连接且未及时释放
  • LiveServerTestCase启动的后台线程未正确回收连接
  • 批量遍历端点时,大量请求的连接累积未关闭

解决措施

1. 限制Hypothesis并发与测试用例数

修改authenticated_strategy中的@settings,禁用并发并减少单端点测试用例数,避免短时间内创建大量连接:

@contextmanager
def authenticated_strategy(strategy, token: str):
    @given(case=strategy)
    # 禁用多线程,限制单端点测试用例数为50(可根据需求调整)
    @settings(deadline=None, workers=1, max_examples=50)
    def f(case):
        case.call_and_validate(headers={"Authorization": f"Bearer {token}"})

    yield f

2. 手动强制回收数据库连接

在每个子测试结束后,手动关闭Django数据库连接,避免连接泄漏:

def test_api(self):
    auth_token = AuthTokenFactory().token
    from django.db import connections
    for endpoint in self.schema:
        for method in self.schema[endpoint]:
            with self.subTest(endpoint=f"{method.upper()} {endpoint}"):
                strategy = self.schema[endpoint][method].as_strategy()
                with authenticated_strategy(strategy, auth_token) as run_strategy:
                    run_strategy()
                # 关闭所有数据库连接
                for conn in connections.all():
                    conn.close()

同时在测试类的tearDown方法中补充连接清理:

def tearDown(self):
    super().tearDown()
    from django.db import connections
    for conn in connections.all():
        conn.close()

3. 降低Schemathesis速率限制

将RATE_LIMIT从10/s调低至5/s或3/s,减少单位时间内的请求量,让连接有足够时间被回收:

RATE_LIMIT = "5/s"

4. 改用WSGI模式替代LiveServerTestCase

如果不需要真实HTTP服务器,直接通过WSGI调用应用,避免LiveServer额外的线程和连接开销:

from django.test import TestCase
from my_app.wsgi import application

class TestApiSchemathesis(TestCase):
    def setUp(self):
        super().setUp()
        self.schema = schemathesis.from_wsgi(
            openapi_schema, application, rate_limit=RATE_LIMIT
        )

5. 临时调整PostgreSQL最大连接数(应急方案)

若CI环境允许,修改PostgreSQL配置postgresql.conf中的max_connections参数(如从100改为200),但此方法仅为临时缓解,优先修复代码层面问题。

调试方法

  • 实时查看连接数:在测试过程中插入SQL查询,打印当前活跃连接数:
    from django.db import connection
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM pg_stat_activity;")
        print(f"Active connections: {cursor.fetchone()[0]}")
    
  • 启用Django连接跟踪:在测试环境的settings.py中设置CONN_MAX_AGE=0,强制每次请求后关闭连接;开启DEBUG=True可查看连接的创建/关闭日志。
  • Hypothesis verbose日志:在@settings中添加verbosity=2,查看每个测试用例的执行细节,确认是否存在并发请求导致的连接暴涨。

内容的提问来源于stack exchange,提问作者tao_oat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:57:04