pgbouncer 作为 PostgreSQL 的连接池代理,无法正确处理 currval() 函数,核心原因是 currval() 依赖数据库会话(session)级别的状态,而连接池的 “连接复用” 机制会破坏这种会话关联性。
1. currval() 的本质:依赖会话级序列状态
currval('sequence_name') 的作用是 获取 “当前会话” 中最后一次通过 nextval() 生成的序列值。它的工作机制有两个关键前提:
必须在同一个数据库会话中,先执行过
nextval()(例如插入语句通过序列生成主键时会隐式调用nextval())。序列状态是会话隔离的:不同会话的
nextval()操作互不影响,currval()只能获取当前会话的历史值。
2. pgbouncer 的连接复用机制:会话关联性被打破
pgbouncer 的核心功能是复用数据库连接,以减少频繁创建 / 销毁连接的开销。当应用程序使用完连接后,pgbouncer 不会真正关闭连接,而是将其放回连接池,供后续请求复用。
这种机制会导致一个问题:应用程序的 “逻辑会话” 与数据库的 “物理连接(会话)” 可能不一致。例如:
应用请求 A 从连接池获取连接 1,执行
insert语句(内部调用nextval()生成序列值 100)。应用请求 A 释放连接,连接 1 被放回连接池(此时连接 1 的会话中,
currval()仍为 100)。应用请求 B 获取连接 1,执行
select currval(...),会错误地获取到 100(这是请求 A 生成的值,与请求 B 的操作无关)。
3. 后果:currval() 返回值不可靠
由于连接池复用了物理连接,currval() 无法区分不同应用请求的 “逻辑会话”,可能返回其他请求生成的序列值,导致:
主键获取错误(例如拿到其他插入操作的 ID)。
业务逻辑异常(如关联数据插入时使用了错误的父 ID)。
极端情况下,若连接被复用前未执行过
nextval(),currval()会直接报错(currval is not yet defined in this session)。
总结
pgbouncer 无法解决 currval() 的问题,根源在于 currval() 依赖会话级状态,而连接池的核心机制是 “复用物理连接”,两者的设计理念存在冲突。
解决方案:
避免使用
currval(),改用nextval()手动生成主键(order="BEFORE"模式)。对 PostgreSQL 而言,优先使用
INSERT ... RETURNING id直接返回主键(无需依赖序列状态)。若必须使用连接池且保留
currval(),可将pgbouncer的连接池模式设为session(而非transaction或statement),但会丧失部分连接复用的性能优势。