psycopg2报connection already closed 重连后是否需重新创建cursor
核心问题结论
你最初的实现无法保证连接长期有效,也没有做主动连接释放的逻辑:
- 代码仅在
Database类初始化时创建一次数据库连接、生成一次游标,之后所有操作都复用这一个连接。PostgreSQL服务端、网络中间件(比如云数据库的负载均衡、防火墙)默认会回收长时间空闲的TCP连接,连接被远端强制断开后,客户端侧不会自动感知,再拿着已经失效的连接执行操作就会抛出connection already closed错误,这也是你代码跑10天突然崩溃的直接原因。 - 你用到的
with self.connection上下文管理器仅负责自动提交/回滚事务,不会自动检测连接存活、不会自动重连,也不会在退出上下文时关闭连接。你的原始代码全程没有主动调用connection.close(),正常运行时客户端不会主动关连接,报错里的关闭都是异常场景下被远端断开导致的。
关于except块是否需要重建游标的问题
必须加这行代码。
游标是和单个数据库连接强绑定的,旧连接已经关闭失效的前提下,原来的self.cursor是依附于死连接的无效对象,哪怕你新建了可用的新连接,用旧游标执行操作依然会报错。
另外你当前修改的版本还有几个明显的逻辑缺陷:
- 把
fetchone()放到finally块是错误逻辑:如果首次执行报错、重连后的重试也报错,finally块里调用无效游标拉取结果会抛出新的未捕获异常;如果后续复用这个方法写增删改操作,fetch逻辑本身就不成立。 - 异常捕获范围不全:网络闪断、连接超时等场景会抛出
psycopg2.OperationalError,仅捕获InterfaceError覆盖不了所有连接失效的场景。 - SQL拼接写法存在注入风险:直接把
user_id拼到SQL语句里是高危写法,哪怕参数是内部生成的,也应该用psycopg2自带的参数化查询能力。 - 全局复用单个游标存在线程安全问题:如果你的程序是多线程/异步模型,多个请求共用一个游标会出现执行状态错乱、结果串读的问题。
修正参考实现
把连接、游标创建的逻辑统一封装,遇到连接失效时全量重建连接和游标,重试一次操作,同时修正上述逻辑问题:
import psycopg2 from psycopg2 import InterfaceError, OperationalError @dataclass class Database: def __init__(self): self.connection = None self.cursor = None self.current_date = utc.localize(datetime.now()) self._init_connect() def _init_connect(self): # 统一处理连接、游标创建,存在有效连接时先回收旧连接 if self.connection and not self.connection.closed: self.connection.close() self.connection = psycopg2.connect(config.DB_URI, sslmode='require') self.cursor = self.connection.cursor() def check_user(self, user_id): try: # 执行操作前先校验连接状态 if self.connection.closed: self._init_connect() with self.connection: # 用参数化查询替代SQL拼接 self.cursor.execute("SELECT user_id FROM mango WHERE user_id = %s", (user_id,)) result = self.cursor.fetchone() return bool(result) except (InterfaceError, OperationalError): # 捕获所有连接类异常,重连后重试一次 self._init_connect() with self.connection: self.cursor.execute("SELECT user_id FROM mango WHERE user_id = %s", (user_id,)) result = self.cursor.fetchone() return bool(result)
如果服务需要长期运行,更稳妥的方案是使用psycopg2自带的连接池能力,由连接池负责连接存活校验、自动回收重建,比手写重连逻辑的稳定性高很多;如果业务并发不高,也可以每次操作时新建连接、操作完成后主动关闭连接,避免长连接被回收的问题。
内容的提问来源于stack exchange,提问作者utikpuhlik
相关产品推荐
相关产品推荐

