连接池让许多应用请求在不同时间借用较少的 PostgreSQL 会话。复用能够摊薄建立连接的成本,也能限制同时借出的数量,但不会让已经过载的数据库自动变快。池本身又增加了一条队列,它的容量、等待时间和取消行为,都属于请求预算的一部分。应把这条队列作为服务行为来设计,而不是把它隐藏在一个“复用更快”的标签后面。
坏掉的样子
数据库后端处于 ClientRead,表示正在等待客户端发数据,并不表示应用请求正在等待借用连接。真正的池内等待者可能还没有数据库连接,因此服务器根本没有对应的后端可供观察。获取连接的等待应在池里测量,查询执行和锁等待应在服务器测量;把它们混成一个所谓数据库池等待事件,会掩盖实际瓶颈的位置。
归还连接也不保证抹掉所有会话状态。Psycopg 的连接上下文在正常退出时提交,在异常离开上下文时回滚;额外重置取决于配置。已经提交的会话级设置,可以继续留给下一个借用者,即使没有泄漏未结束的事务。临时对象和其他会话资源同样需要明确策略,不能因为使用了连接池,就假定所有内容都会自动恢复到初始状态。
快速验证
准备 Python 3.10 或更新版本、PostgreSQL 18 可丢弃数据库,以及允许创建实验模式的角色。原演练使用以下依赖;本轮审阅没有重新执行并发数据库实验。安装方式为:python3 -m pip install "psycopg[binary]==3.3.5" "psycopg-pool==3.3.1"。使用已有认证配置,将 PG_DSN 设置为该测试数据库的连接串。把程序保存为 pool-demo.py,在安装依赖的同一个 Python 环境中运行:
import os
import time
from concurrent.futures import ThreadPoolExecutor
import psycopg
from psycopg_pool import ConnectionPool, PoolTimeout, TooManyRequests
dsn = os.environ['PG_DSN']
with psycopg.connect(dsn, autocommit=True) as admin:
admin.execute('CREATE SCHEMA pool_demo')
try:
admin.execute('CREATE TABLE pool_demo.counter (n integer)')
admin.execute('INSERT INTO pool_demo.counter VALUES (0)')
with ConnectionPool(
dsn, min_size=1, max_size=1, max_waiting=1,
timeout=3, open=True,
kwargs={'application_name': 'pool_example'},
) as pool:
pool.wait(timeout=3)
def queued_request():
try:
with pool.connection(timeout=1):
raise AssertionError('holder should still own the only connection')
except PoolTimeout:
return 'PoolTimeout'
with ThreadPoolExecutor(max_workers=1) as workers:
with pool.connection() as held:
original_pid = held.info.backend_pid
waiting = workers.submit(queued_request)
deadline = time.monotonic() + 0.5
while pool.get_stats()['requests_waiting'] != 1:
if time.monotonic() >= deadline:
raise TimeoutError('request did not enter the pool queue')
time.sleep(0.005)
state = admin.execute(
'SELECT state,wait_event_type,wait_event '
'FROM pg_stat_activity WHERE pid=%s', (original_pid,)
).fetchone()
assert state == ('idle', 'Client', 'ClientRead'), state
print('waiting_in_pool', 1, 'server', state)
try:
with pool.connection(timeout=0.1):
raise AssertionError('queue limit was not applied')
except TooManyRequests:
print('queue_limit', 'TooManyRequests')
assert waiting.result(timeout=2) == 'PoolTimeout'
print('queued_request', 'PoolTimeout')
with pool.connection() as conn:
assert conn.info.backend_pid == original_pid
conn.execute("SET application_name='previous_borrower'")
try:
with pool.connection() as conn:
conn.execute('UPDATE pool_demo.counter SET n=99')
raise ValueError('simulated request failure')
except ValueError as error:
assert str(error) == 'simulated request failure'
with pool.connection() as conn:
value = conn.execute('SELECT n FROM pool_demo.counter').fetchone()[0]
setting = conn.execute('SHOW application_name').fetchone()[0]
assert value == 0 and setting == 'previous_borrower'
assert conn.info.backend_pid == original_pid
print('same_backend', True, 'rollback_value', value, 'retained_setting', setting)
conn.execute('RESET application_name')
finally:
admin.execute('DROP SCHEMA pool_demo CASCADE')
池里只有一条连接,最多允许一个请求排队。主线程借走连接但不执行查询,工作线程尝试借用它。池报告一个等待者以后,独立观察连接看到被借走的数据库后端正空闲等待客户端读取事件。两种状态可以同时存在:应用已经排队,数据库会话却没有正在执行的查询。这正好说明为什么不能用服务器的空闲状态代替池的等待指标。
第三个获取请求因为队列已满,被拒绝并产生 TooManyRequests。原本排队的工作线程则在一秒获取预算耗尽后产生 PoolTimeout。持有者没有提前归还连接,因此实验清楚区分了队列溢出与等待超时。这两种错误都不是 PostgreSQL 的行锁超时,也不能据此认为某条应用 SQL 已经执行过。
主线程归还之后,下一个借用者得到同一个后端标识。它设置会话级应用名字并正常结束,随后另一位借用者把计数器更新为九十九,却抛出异常离开连接上下文。上下文管理器回滚这次更新。最后一次借用看到计数器仍为零,之前提交的会话设置却仍然存在,后端标识也没有改变。
因此,最终输出应同时报告后端复用为真、回滚后的值为零,以及保留的应用名字。事务清理生效了,会话状态却没有被一概清空。程序显式恢复自己的设置,并删除自己创建的模式。如果同名模式原本就存在,初始化会在取得清理所有权之前失败,不应把别人的对象当成实验残留删除。
按完整部署计算容量
池大小限制的是一个池实例,不是整个服务集群。四个进程各自保持八条连接,就可能占用三十二个服务器槽位,还没有计算其他应用、迁移、监控和管理余量。应把这些数量合计后,与数据库可用连接预算及有效执行并发比较。给每个进程都增加连接,可能只是把等待移到服务器里,同时提高内存压力,而不是减少整个请求的耗时。
连接应只在保持正确事务边界所需的完整数据库操作期间借出,所有退出路径都应归还。与数据库无关的网络调用或用户交互,不宜长期占着租用。可以使用事务局部设置来限制影响范围,或者为确实需要清理的状态配置明确重置回调。Psycopg 要求回调结束时连接处于事务外的空闲状态;清理查询如果开启了事务,也必须完成它,否则连接会被丢弃。应同时观察获取延迟、队列拒绝、持有时长和数据库执行,再决定是否扩容;否则,连接泄漏或过长的持有周期很容易被误当成池太小。
来源
- Psycopg 连接池文档源码,连接生命周期、事务上下文与可选重置回调,已核验 2026-10-15。
- Psycopg 连接池 API 文档源码,池大小、队列限制、超时异常与统计,已核验 2026-10-15。
- PostgreSQL 18 连接设置,服务器连接上限与预留容量,已核验 2026-10-15。
- PostgreSQL 18 活动统计,后端状态与客户端读取等待语义,已核验 2026-10-15。