SQLAlchemy:提交后访问Model.id时出现ObjectDeletedError,为啥?
Posted
技术标签:
【中文标题】SQLAlchemy:提交后访问Model.id时出现ObjectDeletedError,为啥?【英文标题】:SQLAlchemy: ObjectDeletedError when accessing Model.id after commit, why?SQLAlchemy:提交后访问Model.id时出现ObjectDeletedError,为什么? 【发布时间】:2012-11-05 04:00:42 【问题描述】:它有时会发生在我的产品环境中(大多数时候都可以)。我怀疑它是否与 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 是一个装饰器,它将在“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.
|
编辑: 我在“session.commit()”后面加上了“return m.id”,仍然会引发 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']
所以它必须与这个主/从配置有关。
你知道如何解决吗?
【问题讨论】:
请在您的生产和开发环境中包含您对 sqlalchemy 的所有配置。 expire_on_commit 似乎很可能是违规者,但我们怎么能不看到你使用的东西就知道呢? 我已经给出了配置。在专业和开发环境中都一样。 【参考方案1】:DBSession 是使用默认值创建的 (expire_on_commit = True)。所以提交后,当返回obj.id时,obj已经过期。所以session会从slave数据库(engines['slave'])获取obj,但是由于主从延迟,对应的记录还没有同步到slave。
【讨论】:
我在复制数据库设置和负载平衡器时遇到了这个问题。最后,SQLAlchemy 正在写入一个后端,然后从另一个读取,这还没有时间同步,导致这个错误。你的回复帮我搞定了。谢谢!【参考方案2】:此错误意味着以下两种情况之一:
raise_my_exception() 实际上并没有引发异常,因此在 rollback() 期间,代码落入“return m.id”并且该行不存在,因为它已回滚。
在您说 session.commit() 和“return m.id”之间,一个并发线程或进程正在删除该行。提交后数据从“m”过期,以便下一次访问将从数据库中检索此对象的最新数据,并将其放入新事务中。这与您“有时(大多数情况下可以)”的描述一致。 - 偶尔发生的问题通常是由于并发问题。
【讨论】:
1. raise_my_exception() 确实引发了异常。 2.DBSession()
是线程本地的,是否还有可能出现并发问题?
'session.commit()' 成功,当错误发生时,我确实在我的数据库中找到了插入行。
任何数量的其他进程或线程都可以随时更改您的数据库状态,并且您当前的线程在您的事务结束时会受到这些更改的影响。
开启 echo='debug' 以查看所有正在发出的 SQL 和正在接收的所有结果集。
我删除了主/从路由,错误消失了,参考Edit2
以上是关于SQLAlchemy:提交后访问Model.id时出现ObjectDeletedError,为啥?的主要内容,如果未能解决你的问题,请参考以下文章