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

相关推荐
Byron__1 小时前
数据库高频面试核心知识点
数据库·面试
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 7 - 9)
开发语言·数据库·python
weixin_BYSJ19871 小时前
基于Django的非物质文化遗产管理系统设计与实现(源码 + 文档)98950
java·javascript·spring boot·python·django·flask·php
Yvonne爱编码1 小时前
数据库---Day10 索引
数据库·sql·mysql
Wonderful U1 小时前
基于Python+Django+psutil的轻量化服务器自动化监控平台实战
服务器·python·django
Jul1en_1 小时前
【Redis】 集群概念
数据库·redis·哈希算法
我是一颗柠檬1 小时前
【Redis】有序集合与位图Day5(2026年)
数据库·redis·后端·缓存
我是一颗柠檬1 小时前
【Redis】持久化机制Day6(2026年)
数据库·redis·后端·缓存·database
huangdong_1 小时前
有什么软件可以下载淘宝和天猫店铺的商品图片?——从工具推荐到技术原理的完整解答
java·前端·数据库