错误详情 sqlalchemy.exc.DBAPIError: (cymysql.err.Error) (<class 'BrokenPipeError'>, BrokenPipeError(32, 'Broken pipe'))
嗯,最近在用sqlalchemy,程序刚开始运行时,一切增删改查都正常,一段时间(经过分析,应该是8小时后)后,会出现上述错误。
先上结论错误解决方案:
1.如果是flask项目,建议直接使用flask_sqlalchemy,即可解决该问题(有效,做过Flask的都知道,比较有意思的问题是,Flask是通过何种方式解决该问题的)
2.每次使用session后,使用session.close()
(有效性未知,程序正在运行,待我长时间运行一下该程序,记录结果,进行验证。
启动时间 2020年12月12日19:01:13 此时运行一切正常)
错误的缘由猜想
首先,该错误给出提示,
(Background on this error at: http://sqlalche.me/e/13/dbapi)
据该页面提示,错误并不是sqlalchemy本身的错误,而是数据库的错误。
进而查看mysql 的log,分析错误原因可能是mysql连接池已经关闭。
而据mysql官方文档说,连接池的有效时间是8小时。
这样,该错误的可能原因就明白了,
我们从sqlalchemy向mysql建立了一个连接,在mysql的连接池有效时间内,程序一切正常。
当到达8小时后,mysql单方面关闭连接池,而sqlalchemy依然在使用该连接池,就会导致BrokenPipeError错误。
而sqlalchemy中使用Session管理连接池。我们需要使用session.close()关闭连接池。再次调用session任务时,因为session已经关闭,sqlalchemy会重新建立一个连接。这样就解决了这个问题。
猜想的验证
一个较好的验证方式是使用try except
如下所示:
....
try:
result = session.do_sth
print(result)
except:
session.close()
result = session.do_sth
print(result)
....
如果该代码有效,则证明上述分析都是正确的。
实际解决方案
当然,在实际代码中,不可能每处调用session,都使用try except
因此,要养成每次使用session,都主动关闭session的习惯。
衍生的问题 Parent instance <Xxx at 0x...> is not bound to a Session
如果我们定义了一个实体类,其使用relationship形成了多对一的关系。如下代码
class Test(Base):
__tablename__ = 'test'
id = Column(Integer, primary_key=True,autoincrement=True)
name = Column(String(50))
test_relationship = relationship('Test2', cascade='all, delete', passive_deletes=True)
test_relationship_id = Column(Integer, ForeignKey("test_relationship.id", ondelete='CASCADE'))
而按照上面说的,每次调用查询后,都关闭。
def test_method():
result = session.query(Test).first()
session.close()
return result.test_relationship.name
此时会报错。
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <Test at 0x217d5f13f98> is not bound to a Session; lazy load operation of attribute 'test_relationship' cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3)
根据提示页面,该错误是因为sqlalchemy对于实体类默认使用的是懒加载模式,也就是在查询Test的时候,并不会直接读取Test2的信息,只有在手动调用test_relationship时,才会去数据库中查询Test2的信息。
而我们如果使用Test2信息之前,已经手动关闭了session,会导致没有办法查询。也就是他说的Test没有和session绑定(因为你自己调用了close)。
分析到这,就没明白了,我们只要在查询Test的时候,让Sqlalchemy直接load的Test2的信息,即可解决。
而实现该目标的方法,如下:
from sqlalchemy.orm import joinedload
def test_method():
result = session.query(Test).options(joinedload('test_relathionship')).first()
session.close()
return result.test_relationship.name
该问题即可解决。