← 返回题目列表

异步 Python 中数据库连接池怎么管理?为什么不能每个请求都新建连接?

高频 中等 第 3 / 27 题 更新于 2026/07/31
异步数据库连接池asyncpgSQLAlchemy

简化版

异步 Web 服务里数据库连接池用于复用有限连接,避免每个请求都新建 TCP 连接、认证和初始化。连接池要设置最大连接数、超时、生命周期,并确保每次使用后释放回池;否则容易出现连接泄漏、数据库被打满或事件循环被阻塞。

详细版

异步框架能同时处理很多请求,但数据库连接不是无限的。如果每个请求都新建连接,连接建立成本高,还可能瞬间打爆数据库 max_connections。连接池会提前或按需创建一组连接,请求需要时借出,用完归还。

pool = await asyncpg.create_pool(dsn, min_size=1, max_size=10)

async with pool.acquire() as conn:
    rows = await conn.fetch("select * from users limit 10")

面试重点是:异步不等于无限并发;连接池大小要和数据库能力、应用实例数、请求耗时一起算;同步数据库驱动不能直接放在 async 函数里阻塞事件循环。

完整版教学

一、为什么不能每次请求新建连接

数据库连接建立包含 TCP 握手、认证、参数协商、权限初始化等步骤。每个请求都新建连接会增加延迟,也会让数据库频繁创建和销毁后端会话。高并发时更危险。

假设一个服务每秒 500 请求,每次都新建连接且连接平均存活 100ms,数据库瞬时可能承受几十到上百个连接波动。如果有 10 个应用实例,压力再乘以 10。

请求 -> 新建连接 -> 查询 -> 关闭
请求 -> 新建连接 -> 查询 -> 关闭

连接池复用连接,把这段变成“借连接、用连接、还连接”,减少重复成本。

二、异步连接池如何工作

连接池内部维护一组可用连接。请求协程调用 acquire 时,如果池里有空闲连接就借出;没有空闲但没到上限就新建;达到上限则等待或超时。

pool max_size = 10
空闲连接: 3
请求进来 -> acquire -> 借出 1 条
用完 -> release -> 回到空闲
参数含义影响
min_size最小连接数冷启动准备
max_size最大连接数数据库压力上限
timeout获取连接等待时间避免无限挂起
max_lifetime连接寿命避免旧连接异常

异步服务并发高,数据库连接池更要设上限;没有上限的并发只是把压力转嫁给数据库。

三、连接池大小怎么估算

连接池不能只看单个应用。要用“应用实例数 × 每实例连接池上限”去估算数据库总连接数。数据库还要留给管理工具、迁移任务、后台 worker 和其他服务。

例如数据库允许 200 个连接,系统有 8 个 Web 实例和 2 个 worker 实例。如果每个实例池上限设 30,总上限是 300,已经超过数据库能力。更合理可能是 Web 每个 15,worker 每个 10,总计 140,再留余量。

8 * 15 + 2 * 10 = 140
DB max_connections = 200
余量 = 60

实际还要压测,看查询耗时、慢 SQL、事务持有时间和连接等待时间。

四、连接泄漏怎么发生

连接泄漏是借出连接后没有归还。异步代码里如果中途异常、取消、超时,而你没有用上下文管理器或 finally 释放连接,就可能让池里的可用连接越来越少。

坏写法:

conn = await pool.acquire()
rows = await conn.fetch(sql)
# 异常时可能没 release
await pool.release(conn)

好写法:

async with pool.acquire() as conn:
    rows = await conn.fetch(sql)

async with 能在异常路径下归还连接。生产中还要监控池等待时间、借出连接数和超时次数。

五、同步驱动为什么会阻塞事件循环

如果在 async 函数里直接用同步数据库驱动,例如普通 psycopg2 或 PyMySQL,同步查询会阻塞当前线程。事件循环在这段时间无法调度其他协程,异步服务会退化。

async def handler():
    cursor.execute("select pg_sleep(1)")  # 同步阻塞,危险

应使用异步驱动或异步 ORM 支持,例如 asyncpg、SQLAlchemy async engine、aiomysql 等。实在只能用同步库,可以放到线程池,但这会引入线程池容量和上下文切换成本,不是首选。

六、事务和连接占用的关系

事务期间连接会被占用。长事务不仅影响数据库锁和 MVCC,也会占住连接池资源。异步代码里如果在事务中 await 外部 HTTP 请求,连接可能被白白占着等待网络。

BEGIN
  查询 DB
  await 调第三方 API 2s  <- 连接和事务都被占着
COMMIT

更好的方式是缩短事务范围:先做外部调用或准备数据,再进入事务完成必须原子的数据库操作。连接池问题很多时候不是池太小,而是连接被慢事务占用太久。

七、常见误区与追问

  • 误区:异步服务可以开无限数据库连接。 数据库连接是昂贵资源,必须用池和上限控制。
  • 误区:连接池越大越好。 过大会把数据库打满,增加上下文切换和锁竞争。
  • 误区:async 函数里用同步驱动没问题。 同步 I/O 会阻塞事件循环,影响所有协程。
  • 追问:连接泄漏怎么防?async with、finally 释放,并监控池借出和等待情况。
  • 追问:池大小怎么算? 按实例数乘池上限估算总连接,并结合数据库上限和压测调整。
  • 追问:为什么长事务影响连接池? 事务持有期间连接不能归还,其他请求只能等待。
  • 追问:连接池满了怎么办? 等待、超时、限流、优化慢 SQL、缩短事务或扩数据库能力。

八、加强记忆

异步数据库连接池记成“连接有限,并发要排队”。池负责复用连接和限制上限,async with 负责归还,异步驱动负责不阻塞 loop,短事务负责不长时间占连接。回答这题时把池大小按实例总数计算出来,比只说“用连接池提高性能”更有工程含量。