Orm SQLAlchemy:ObjectDeletedError在提交后访问Model.id时,为什么?

Orm SQLAlchemy:ObjectDeletedError在提交后访问Model.id时,为什么?,orm,sqlalchemy,Orm,Sqlalchemy,它有时发生在我的产品环境上(大多数情况下是可以的)。我怀疑它是否与sessionmaker func中的参数'expire_on_commit'有关 @close_session def func(): session = DBSession() # scoped_session, thread_local m = Model() m.content = 'content' session.add(m) try: session.commi

它有时发生在我的产品环境上(大多数情况下是可以的)。我怀疑它是否与sessionmaker func中的参数'expire_on_commit'有关

@close_session
def func():
    session = DBSession() # scoped_session, thread_local
    m = Model()
    m.content = 'content'
    session.add(m)
    try:
        session.commit()
    except SQLAlchemyError as e:
        session.rollback()
        raise_my_exception()
    return m.id
close_session是一个decorator,它将在“finally”部分执行“DBSession().close()”。 ObjectDeleteError发生在第行“return m.id”

SQLAlchemy配置:

engines = {                                                                        
    'master': create_engine(                                                       
        settings.MASTER_URL, echo=settings.ECHO_SQL, pool_recycle=3600),           
    'slave': create_engine(                                                        
        settings.SLAVE_URL, echo=settings.ECHO_SQL, pool_recycle=3600),            
}                                                                                  


class RoutingSession(Session):                                                     
    def get_bind(self, mapper=None, clause=None):                                  
        #return engines['master']                                                  
        if self._flushing:                                                         
            return engines['master']                                               
        else:                                                                      
            return engines['slave']                                                

DBSession = scoped_session(sessionmaker(class_=RoutingSession))
ObjectDeletedError文档:

class ObjectDeletedError(sqlalchemy.exc.InvalidRequestError)
 |  A refresh operation failed to retrieve the database
 |  row corresponding to an object's known primary key identity.
 | 
 |  A refresh operation proceeds when an expired attribute is
 |  accessed on an object, or when :meth:`.Query.get` is
 |  used to retrieve an object which is, upon retrieval, detected
 |  as expired.   A SELECT is emitted for the target row
 |  based on primary key; if no row is returned, this
 |  exception is raised.
 | 
 |  The true meaning of this exception is simply that
 |  no row exists for the primary key identifier associated
 |  with a persistent object.   The row may have been
 |  deleted, or in some cases the primary key updated
 |  to a new value, outside of the ORM's management of the target
 |  object.
 |  

编辑: 我将“return m.id”放在“session.commit()”之后,仍然会引发ObjectDeletedEror

@close_session
def func():
    session = DBSession() # scoped_session, thread_local
    m = Model()
    m.content = 'content'
    session.add(m)
    try:
        session.commit()
        return m.id
    except SQLAlchemyError as e:
        session.rollback()
        raise_my_exception()

编辑2:

我将RoutingSession更改为仅返回master,错误消失了:

class RoutingSession(Session):                                                     
    def get_bind(self, mapper=None, clause=None):                                  
        return engines['master']                                                               
所以它一定与主/从配置有关


你知道怎么解决吗?

这个错误意味着两件事之一:

  • raise_my_exception()实际上并没有引发异常,因此在rollback()过程中,代码落入“return m.id”中,行在回滚时不存在

  • 在您说session.commit()和“return m.id”之间,并发线程或进程正在删除该行。数据在提交后从“m”过期,因此下一次访问将从数据库检索此对象的最新数据,并将其放入新事务中。这与您描述的“有时(大多数情况下是可以的)”-偶尔发生一次的问题通常是由于并发性问题造成的


  • DBSession是使用默认值创建的(expire\u on\u commit=True)。因此,在提交之后,当返回obj.id时,obj已过期。所以会话将从从数据库(引擎['slave'])获取obj,但由于主从延迟,相应的记录尚未同步到slave

    请包括您的生产和开发环境的所有sqlalchemy配置。“在提交时过期”似乎是一个可能的违规者,但我们如何知道您使用了什么?我已经给出了配置。在pro和dev环境中都是一样的。raise_my_exception()确实引发了异常。2.
    DBSession()
    是线程本地的,是否仍然可能存在并发问题?如果“session.commit()”成功,则在发生错误时,我会在数据库中找到插入行。任何数量的其他进程或线程都可以随时更改数据库的状态,在事务结束时,您当前的线程将暴露于这些更改中。启用echo='debug'可查看所有SQL的发出和所有结果集的接收。我已删除主/从路由,错误消失,请参阅
    Edit2
    我在复制数据库设置和负载平衡器中遇到此问题。最后,SQLAlchemy向一个后端写入数据,然后从另一个后端读取数据,这导致了这个错误,当时还没有时间同步。你的回答帮我搞定了。谢谢