Django中MySQL Cursor上下文管理引发同步错误问题咨询
在Django框架中使用MySQL,之前的代码未关闭Cursor但运行稳定,为解决资源泄漏问题,将Cursor改写为用__enter__/__exit__实现上下文管理后,程序变得不稳定。
自定义Cursor和DB类代码如下:
class Cursor: def __init__ (self, db): self.db = db self.cursor = db.DB_connection.cursor () def __enter__ (self): return self def __exit__ (self, ex_type, ex_value, ex_traceback): if ex_value is not None: # log stuff self.cursor.close () self.cursor = None def __del__ (self): if self.cursor is not None: # log stuff self.cursor.close () def one (self, query, args): self.cursor.execute (query, args) return self.cursor.fetchone () class DB: def __init__ (self, my_stuff): self.DB_connection = connect (...) def get (self): return Cursor (self)
应用中的调用方式:
with DB_connection.get() as db: result = db.one ("SELECT ...", ...)
目前程序出现两个异常情况:
- 偶尔随机抛出错误(异常出现在Cursor的
__exit__方法中):
_mysql_connector.MySQLInterfaceError: Commands out of sync; you can't run this command now
按错误提示,这意味着游标存在未取完的查询结果,但每次调用都会创建新Cursor,逻辑上矛盾。
2. 进程有时无异常信息直接退出,Docker日志显示:
www_1 | "GET /test_page HTTP/1.1" 200 16912 docker_www_1 exited with code 245
已通过打印语句确认同一时间最多存在1个Cursor,除了改用上下文管理关闭Cursor外无其他显著改动。请问当前的API使用方式是否正确?
当前API使用方式存在多个关键问题,直接导致了程序不稳定:
1. 数据库连接复用冲突
你的DB类复用了同一个数据库连接(DB_connection),即使每次创建新的Cursor,MySQL的底层连接也不允许同时存在多个未完成的操作。虽然你确认同一时间只有1个Cursor,但Python的线程调度、网络延迟等情况可能导致:前一个Cursor的查询结果还未完全从MySQL端读取完毕,连接就被下一个Cursor复用,触发Commands out of sync错误——哪怕你调用了fetchone(),MySQL仍可能有未传输完成的结果数据包,关闭Cursor时就会触发异常。
2. __del__方法的安全风险
Python的垃圾回收时机完全不确定,__del__可能在__exit__执行后再次被调用。如果__exit__已经关闭Cursor并将self.cursor设为None,极端情况下(比如存在引用循环),__del__会尝试关闭一个已不存在的Cursor,甚至在数据库连接已经被销毁后进行操作,直接导致进程崩溃(退出码245通常和非法资源访问、信号终止相关)。
3. 上下文管理的事务逻辑缺失
你的__exit__方法没有处理事务的提交或回滚逻辑。如果with块内发生异常,数据库连接的事务状态会残留,影响后续基于该连接的所有操作,进一步加剧不稳定。
修复方案
改用连接池或独立连接
不要让多个Cursor共用同一个数据库连接,使用mysql-connector-python的连接池功能,或者每次创建Cursor时新建一个连接(用完即关闭)。这样能彻底避免同一连接上的操作冲突。移除
__del__方法
上下文管理的__exit__已经确保Cursor会被关闭,__del__属于冗余且危险的操作,直接删除即可。完善
__exit__的事务处理
在__exit__中根据异常情况处理事务,确保连接状态干净:def __exit__ (self, ex_type, ex_value, ex_traceback): try: if ex_value is not None: self.db.DB_connection.rollback() # 记录异常日志 else: self.db.DB_connection.commit() finally: self.cursor.close() self.cursor = None优先使用Django原生数据库操作
既然基于Django开发,完全没必要自行封装Cursor和连接管理。Django的django.db.connection已经实现了完善的上下文管理、连接池和资源回收机制,直接使用即可:from django.db import connection with connection.cursor() as cursor: cursor.execute("SELECT ...", (...)) result = cursor.fetchone()这是最稳定且符合Django规范的实现,能彻底避免资源泄漏和连接冲突问题。
内容的提问来源于stack exchange,提问作者spraff

