跳转到主内容
极星编程网:以代码为星,赴技术山海!

如何在Python中实现异步的PostgreSQL操作_使用asyncpg驱动提升并发性能

asyncpg性能最强因直连二进制协议,生产必须用create_pool()连接池而非connect();min_size设为平均并发1.2–1.5倍,max_size不超数据库max_connections的70%,需设max_inactive_time=300秒防泄漏。
asyncpg
是目前 Python 生态中性能最强的异步 PostgreSQL 驱动,它不走 DB-API 兼容层、不依赖 psycopg2,而是直接实现 PostgreSQL 二进制协议,因此在高并发读写场景下优势明显。如果你的应用正面临连接阻塞、QPS 上不去、或
psycopg2
在 asyncio 环境中被迫用线程池兜底的窘境,换用
asyncpg
并合理调优,通常能带来 3–10 倍的吞吐提升。 为什么不能直接用
asyncpg.connect()
处理并发请求 单次
asyncpg.connect()
创建的是独占连接,无法复用;在 Web 请求中每来一个请求就新建连接,很快会耗尽数据库连接数(PostgreSQL 默认
max_connections=100
),并触发
asyncpg.exceptions.TooManyConnectionsError
。更严重的是,TCP 握手 + SSL + 认证开销会让首字节延迟飙升。 正确做法是全程使用连接池:
asyncpg.create_pool()
是唯一推荐的生产级入口,它内部管理连接生命周期、自动重连、支持连接预热
min_size
建议设为应用平均并发请求数的 1.2–1.5 倍(例如常驻 8 个活跃连接,设
min_size=10
)
max_size
不宜盲目拉高——超过数据库
max_connections
的 70% 就可能引发拒绝连接;建议先压测再定(常见值:15–30) 务必设置
max_inactive_time=300
(单位秒),否则空闲连接长期不释放,会卡住连接池回收逻辑
executemany()
批量插入时为何有时比循环
execute()
还慢 这不是
asyncpg
的 bug,而是误用了批量接口的典型表现。当传入的
data
是生成器(
generator
)、或元素数量极少(
len(data) < 5
)、或字段类型混杂(比如混合了
None
和
datetime
)时,
executemany()
内部的参数序列化反而增加 CPU 开销。 立即学习 “ Python免费学习笔记(深入) ”; 实战建议: Python 3.14.3 微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。 下载 确保
data
是 list 或 tuple,且长度 ≥ 10;小于该阈值直接用
execute()
+
VALUES ($1,$2),($3,$4)
拼接更稳 避免在
executemany()
中混用
json
和
bytea
字段——不同编解码路径会触发多次缓存 miss 若需动态列名(如 ETL 场景),不要强行塞进
executemany()
,改用事务包裹多个
execute()
并显式
await conn.executemany(...)
事务里用
fetch()
后再
execute()
报
asyncpg.exceptions.InFailedSQLTransaction
这是 PostgreSQL 协议层的硬性限制:一旦事务中某条语句出错(哪怕只是
WHERE id = -1
查不到数据),整个事务就进入“failed”状态,后续任何非
ROLLBACK
操作都会被拒绝。 常见踩坑点:
conn.fetchrow("SELECT ...")
返回
None
本身不是错误,但若紧接着执行
conn.execute("UPDATE ...")
而没做
if row is not None:
判断,逻辑上可能漏掉分支,但不会报这个错 真正触发该异常的是类似
conn.execute("INSERT INTO t(pk) VALUES (1)")
因主键冲突抛出
UniqueViolationError
后,还继续发其他
execute()
解决方式只有两个: 显式捕获异常后
await tx.rollback()
,或改用
SAVEPOINT
隔离子操作(
await tx.execute("SAVEPOINT sp1")
→ 出错时
await tx.execute("ROLLBACK TO sp1")
) 缓存语句(
statement_cache_size
)设多大才合适
asyncpg
默认开启语句缓存(
statement_cache_size=100
),它把已解析的 SQL 文本 + 类型映射存内存里,避免重复解析。但缓存不是越大越好。 关键判断依据: 如果应用中 90% 的查询来自固定模板(如
"SELECT * FROM users WHERE status = $1"
),保持默认 100 完全够用 若大量使用动态 SQL(比如拼接
ORDER BY
字段、不定长
IN ($1, $2, ..., $N)
),缓存命中率会暴跌,此时设为
0
反而降低内存抖动 注意:缓存大小只影响客户端,不影响 PostgreSQL 服务端的 prepared statement;
asyncpg
不自动使用服务端预编译,除非你手动调用
conn.prepare()
实际部署时最容易被忽略的是连接池和事务的生命周期绑定关系:连接池里的每个连接都维护独立的事务状态,
acquire()
得到的连接若未在作用域内完成
commit()
或
rollback()
,该连接会被标记为“不可复用”,直到超时后强制关闭。这意味着——事务必须在
async with pool.acquire() as conn:
块内结束,不能跨
await
拆开。

相关文章