解决问题: sqlalchemy.orm.exc.DetachedInstanceError ...

今天在使用sqlalchemy时,出现一例sqlalchemy.orm.exc.DetachedInstanceError异常,值得简单记录一下过程

问题出现

Traceback (most recent call last):
  File "/Users/dennis/Documents/Projects/PythonProjects/TenMock/tencloud-sdk/tensdk/store/key_pair/service.py", line 166, in <module>
    service.create_account('123Sadfaf', 'test', '', 'data')
  File "/Users/dennis/Documents/Projects/PythonProjects/TenMock/tencloud-sdk/tensdk/core/decorator.py", line 28, in wrapper
    return func(*args, **kwargs)
  File "/Users/dennis/Documents/Projects/PythonProjects/TenMock/tencloud-sdk/tensdk/store/key_pair/service.py", line 58, in create_account
    return biz.id
  File "/Users/dennis/PycharmProjects/tencloud-sdk/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 465, in __get__
    return self.impl.get(state, dict_)
  File "/Users/dennis/PycharmProjects/tencloud-sdk/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 906, in get
    value = state._load_expired(state, passive)
  File "/Users/dennis/PycharmProjects/tencloud-sdk/lib/python3.7/site-packages/sqlalchemy/orm/state.py", line 667, in _load_expired
    self.manager.expired_attribute_loader(self, toload, passive)
  File "/Users/dennis/PycharmProjects/tencloud-sdk/lib/python3.7/site-packages/sqlalchemy/orm/loading.py", line 1345, in load_scalar_attributes
    "attribute refresh operation cannot proceed" % (state_str(state))
sqlalchemy.orm.exc.DetachedInstanceError: Instance <TenAccountInfo at 0x1081e0d50> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/14/bhk3)

异常很明显指向service.py中的create_account函数,我们来看一下该函数。

@validated(CreateAccountOfKeyPairSchema)
def create_account(self, app: str, subject: str, custom: str, data: str) -> str:
    biz = TenAccountInfo()
    biz.id = self._account_info_id_generator.next()
    biz.app = app
    biz.subject = subject
    biz.custom = custom
    biz.data = data
    biz.invalid = 0
    biz.version = self._version_generator.next()

    self._account_info_dao.save(biz)
    return biz.id

粗粗一看,觉得挺正常的。做为习惯面向百度编程的我,把sqlalchemy.orm.exc.DetachedInstanceError在百度上一搜,还真有解决方案。网上方案基本是修改sessionmaker参数(expire_on_commit=False)。

Session = sessionmaker(bind=engine, expire_on_commit=False)

于是,尝试着改了一下,果然问题解决。

问题到这里结束了么?当然没有。
expire_on_commit这个参数缺省值为True,看名字就能知道个七七八八,我也不多说(怕说错误导大家)。改了缺省值,总是不得劲,直觉会影响效率。

我回过头又看了看代码错误的地方create_account,最特殊的就在最后两行:

self._account_info_dao.save(biz)
return biz.id

本意是保存了之后,再返回业务对象biz的id。

在看看save的代码:

def save(self, biz: TenAccountInfo) -> bool:
    with self._session_cls() as session:
        try:
            session.add(biz)
            session.commit()
        except IntegrityError as ex:
            print(ex.__cause__)
            session.rollback()
            return False
        else:
            return True

这里使用with语句,会自动执行session.close()。嗯,保持及时释放资源是个好习惯,不应该有错误。

所以,综合上述两段代码,可以猜想,错误是在session.close()后,return biz.id时,系统重新尝试使用session从数据库中刷新biz对象,而得到的错误。

于是,针对当前问题,可以通过保存id的方式,简单地解决。

解决此次问题的代码如下

@validated(CreateAccountOfKeyPairSchema)
def create_account(self, app: str, subject: str, custom: str, data: str) -> str:
    biz = TenAccountInfo()
    biz.id = self._account_info_id_generator.next()
    biz.app = app
    biz.subject = subject
    biz.custom = custom
    biz.data = data
    biz.invalid = 0
    biz.version = self._version_generator.next()

    ret = biz.id
    self._account_info_dao.save(biz)
    return ret

总结:碰到问题时,不能简单地吞掉错误,而是需要从根本原因入手解决。

上一篇:MySQL全文检索


下一篇:Maxwell “Couldn‘t find database mysql“问题排查