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 从"能跑"到"扛得住"的必经之路。

相关推荐
笃行3502 分钟前
数据库性能优化的五处内核改造,KingbaseES 配了四件诊断工具
数据库
非优秀程序员2 小时前
第02章 杭电的英语老师与一台服务器|《互联网产品恩仇录》
数据库·node.js·代码规范
SelectDB7 小时前
SelectDB 是哪个公司的?是国产数据库吗?一篇讲清飞轮科技与 Apache Doris 的关系
数据库
这个DBA有点耶7 小时前
库内计算推理:一条SQL背后的算子融合,和外挂方案差了几次网络往返
数据库·人工智能·sql
这个DBA有点耶9 小时前
函数索引的“完全一致”有多苛刻:写法、字符集、类型,差一处就失效
数据库·mysql·代码规范
星恒随风9 小时前
MySQL 从零入门(三):表的约束详解
linux·服务器·数据库·笔记·mysql
Alluxio10 小时前
Alluxio + OCI:为 AI 工作负载实现亚毫秒访问延迟与可扩展吞吐
运维·数据库·人工智能·分布式·缓存·ai·语言模型
数据库急诊室_阿哲14 小时前
一次加索引建议的复核记录:从报告到签字
数据库·ai数据库
青瓦梦滋14 小时前
Redis主从复制
服务器·数据库·redis·缓存·bootstrap
慧都小妮子15 小时前
Wyn商业智能国产数据库适配:数据接入、分析缓存与审计日志验证清单
数据库·数据分析·数据可视化·审计日志·商业智能