Django 模型查询中的数据库连接池配置指南

写给每一个被 too many connections 报错折磨过的 Django 开发者。


一、先搞清楚:Django 默认是怎么管连接的?

Django 自带的 django.db.backends 在每次请求结束时,会根据 CONN_MAX_AGE 决定是否关闭数据库连接:

python 复制代码
# settings.py 默认配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'CONN_MAX_AGE': 0,  # 每次请求后都关闭连接
    }
}
  • CONN_MAX_AGE = 0:请求结束立刻断开,下个请求重新建连。安全但慢。
  • CONN_MAX_AGE = None:永久复用同一个连接。快但有风险(连接断开后不会自动重连)。
  • CONN_MAX_AGE = 60:60 秒内复用连接,超时后重建。这是官方推荐的折中方案。

但这只是 Django 层面的"连接复用",不是连接池。当并发上来,每个 Worker 进程仍会各自持有连接,连接数 = Worker 数 × 并发请求数,数据库照样扛不住。


二、什么时候你真正需要连接池?

场景 是否需要连接池
开发环境,QPS < 10 ❌ 不需要
生产环境,Gunicorn/uWSGI 多 Worker,QPS > 50 ✅ 强烈建议
使用 Serverless(如 AWS Lambda) ✅ 需要,否则冷启动+连接数爆炸
数据库连接数上限较低(如 RDS 默认 100) ✅ 必须上

核心矛盾:应用层想复用连接,但 Django 默认的复用粒度太粗(按进程),不够细(按请求)。


三、四种主流方案,从简单到生产级

方案一:调大 CONN_MAX_AGE(最简单,但不够)

python 复制代码
DATABASES = {
    'default': {
        'CONN_MAX_AGE': 600,  # 10 分钟内复用
    }
}

优点:零依赖,一行代码搞定。

缺点:仍然是进程级复用,没法限制总连接数。适合轻量场景。


方案二:django-db-connection-pool(推荐入门)

这是一个专门为 Django 设计的连接池库,基于 DBUtils.PersistentDB,支持 PostgreSQL / MySQL。

安装

bash 复制代码
pip install django-db-connection-pool

配置

python 复制代码
DATABASES = {
    'default': {
        'ENGINE': 'django_db_connection_pool.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypass',
        'HOST': 'localhost',
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,        # 池中最大连接数
            'MAX_OVERFLOW': 5,      # 超出池大小后最多额外创建的连接数
            'RECYCLE': 3600,        # 连接最大存活时间(秒)
            'TIMEOUT': 30,          # 获取连接的等待超时(秒)
        },
    }
}

关键参数说明

参数 含义 建议值
POOL_SIZE 池中常驻连接数 Worker 数 × 2
MAX_OVERFLOW 突发时额外允许的连接数 POOL_SIZE 的 50%
RECYCLE 强制回收连接的周期 3600(1小时)
TIMEOUT 拿不到连接时的等待时间 30 秒

效果: 原本 10 个 Worker 各自建 10 个连接 = 100 个连接,现在 10 个 Worker 共用 10 个连接的池,峰值最多 15 个。


方案三:PgBouncer(PostgreSQL 生产环境标配)

django-db-connection-pool 是应用层连接池,性能和稳定性不如独立的连接池中间件。PostgreSQL 官方推荐用 PgBouncer。

架构变成:

复制代码
Django App → PgBouncer (连接池) → PostgreSQL

快速部署(Docker)

yaml 复制代码
# docker-compose.yml
services:
  pgbouncer:
    image: edoburu/pgbouncer
    environment:
      - DATABASE_URL=postgres://user:pass@postgres:5432/mydb
      - POOL_MODE=transaction
      - MAX_CLIENT_CONN=1000
      - DEFAULT_POOL_SIZE=25
    ports:
      - "6432:6432"

Django 配置(连 PgBouncer,不连 PG 直接)

python 复制代码
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': 'pgbouncer',  # 注意:不是原来的 pg 主机
        'PORT': '6432',       # PgBouncer 端口
        'NAME': 'mydb',
        # ... 其他不变
        'CONN_MAX_AGE': 0,    # 必须设为 0!因为 PgBouncer 自己管池
    }
}

⚠️ 关键坑:CONN_MAX_AGE 必须设为 0。

原因:PgBouncer 的 transaction 模式下,如果 Django 持有连接不放,会导致连接无法回池。设为 0 让 Django 用完即还,PgBouncer 负责复用。

三种 Pool Mode 对比

模式 优点 缺点 适用场景
session 支持所有 PG 特性 无法复用连接,效果差 不推荐
transaction 真正的连接复用,性能最好 不支持 Prepared Statement 大多数 Django 场景推荐
statement 支持 Prepared Statement 某些驱动兼容问题 特殊需求

方案四:使用 psycopg2 自带的连接池(轻量替代)

如果你不想引入额外组件,可以用 psycopg2 内置的 ThreadedConnectionPool

python 复制代码
# settings.py
import psycopg2
from psycopg2 import pool

# 创建全局连接池(Django 启动时初始化)
connection_pool = psycopg2.pool.ThreadedConnectionPool(
    minconn=1,
    maxconn=10,
    host='localhost',
    database='mydb',
    user='myuser',
    password='mypass'
)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        # 不配置 HOST/PORT/USER/PASSWORD,用自定义的 getconn
        'CONN_MAX_AGE': None,
    }
}

# Monkey patch:让 Django 用我们的池
from django.db.backends.postgresql.base import DatabaseWrapper

def get_connection(self):
    conn = connection_pool.getconn()
    return conn

DatabaseWrapper.get_new_connection = get_connection

# 归还连接
def close_connection(self, conn):
    connection_pool.putconn(conn)
    self.connection = None

DatabaseWrapper.close = close_connection

优点:无第三方依赖。

缺点:需要 monkey patch,升级 Django 可能失效,不够优雅。


四、怎么选?一张表说清楚

方案 复杂度 性能 稳定性 推荐场景
CONN_MAX_AGE ⭐⭐ ⭐⭐⭐ 开发/轻量生产
django-db-connection-pool ⭐⭐ ⭐⭐⭐ ⭐⭐⭐ 中小项目,快速上线
PgBouncer ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 中大型生产环境
psycopg2 内置池 ⭐⭐ ⭐⭐⭐ ⭐⭐ 不想加依赖的小项目

五、生产环境的完整配置示例(推荐组合)

python 复制代码
# settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': env('DB_PASSWORD'),
        'HOST': 'pgbouncer',     # 指向 PgBouncer
        'PORT': '6432',
        'CONN_MAX_AGE': 0,        # ⚠️ 关键:必须为 0
        'OPTIONS': {
            'connect_timeout': 10,
            'application_name': 'myapp',  # 方便 PG 侧监控
        },
    }
}

# Gunicorn 配置(配合连接池)
# gunicorn.conf.py
workers = 4               # Worker 数 = POOL_SIZE / 2 左右
worker_class = 'gevent'   # 或 sync,取决于你的视图是否有阻塞 IO
timeout = 30
keepalive = 5

六、监控:你怎么知道连接池生效了?

sql 复制代码
-- PostgreSQL 查看当前连接数
SELECT count(*), state 
FROM pg_stat_activity 
GROUP BY state;

-- 查看 PgBouncer 池状态(连接 PgBouncer 的管理库)
SHOW POOLS;
SHOW CLIENTS;

关键指标:

  • active connections 应远小于 max_connections
  • waiting requests 应接近 0
  • 如果 waiting requests 持续 > 0,说明池太小,需要调大 POOL_SIZE

总结

核心观点 说明
CONN_MAX_AGE 不是连接池 只是进程级复用,不够用
中小项目先上 django-db-connection-pool 一行 ENGINE 换掉,零学习成本
生产环境请上 PgBouncer 稳定、可观测、官方推荐
用 PgBouncer 时 CONN_MAX_AGE 必须为 0 否则连接无法回池,池白搭
池大小 ≈ Worker 数 × 2 留 50% 余量应对突发

连接池不是银弹,但它是 Django 从"能跑"到"扛得住"的必经之路。

相关推荐
笨鸟先飞,勤能补拙12 分钟前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
天桥下的卖艺者21 分钟前
使用scitable包,两步生成逆概率删失权重(IPCW)
数据库·r语言
隔窗听雨眠44 分钟前
AI原生数据库浪潮:国产数据库的架构重构与路径之争
数据库
数据库小学妹1 小时前
数据库选型实战:从数据类型到TCO成本,五维决策框架+九款产品横评
数据库·信创·国产数据库·数据库选型·oracle迁移
神龙天舞20012 小时前
MySQL 备库为什么会延迟好几个小时
android·数据库·mysql
丙氨酸長鏈3 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
独行侠影a4 小时前
APScheduler+Redis 分布式定时任务:解决多实例任务重复执行
数据库·redis·分布式
传说故事4 小时前
数据库中一些常用英文单词含义
数据库·oracle
夏贰四5 小时前
中小企业搭建业务中台如何控成本?业务中台轻量化落地分几步实施?
数据库·业务中台
林焱RPA5 小时前
影刀RPA200篇纪念版:最实用的20个自动化场景大盘点
数据库