Plone + SqlAlchemy + MySql中未提交的事务

时间:2013-01-11 10:49:03

标签: mysql sqlalchemy plone zope

我们有一个混合Web应用程序,使用collective.tin,collective.lead和SqlAlchemy将MySql数据库与Plone集成(最后升级到Plone 4.0)。

好的,我知道集体.tin从未被释放,集体。铅已被取代;然而,几年以来所有事情都(几乎)完美无缺。

最近我们遇到了一种非常奇怪的行为,并且正在寻求帮助以便理解它。

其中,我们有2个Plone内容类型,比如A和B,由子类化collective.tin和相应的innodb MySql表定义; B行有一个指向A的外键。

在15-20分钟的时间跨度内,2个不同的用户创建了3个A对象和10-20个B对象,这些对象未提交给MySql但被Plone索引;我用linux shell中的MySql客户端执行的查询无法找到那些A行(没有查找B行);但是,这两个用户以及其他用户通过Web应用程序(前面提到的组件堆栈)执行的查询偶尔仍然可以找到并正确地显示这些3A对象中的一些。

只有在我重新启动Zope实例后,才有可能从Plone Web界面恢复正常活动; 3 MySql数据库中仍然缺少行和许多B行,但是自动增量计数器显示了预期的增量;我不得不从Plone索引中移除3个无效的A对象大脑(不用担心B对象)。

有关可能原因和如何调查问题的任何建议吗?

1 个答案:

答案 0 :(得分:4)

我们遇到了与sqlalchemy 0.4完全相同的问题;会话将与实际的数据库内容不同步。在我们的案例中,问题有点掩盖,因为用户通过会话亲缘关系被发送到集群中的特定后端。如果亲和力突然消失,消息就会消失。确切的细节有点模糊,因为我无法找到我所采用的修复的正确(古代)修订历史。

从我可以从上下文中收集到的是,会话标识映射可以防止会话要求数据库获取之前检索的对象。因此,它不会在不同的会话中看到对这些对象所做的更改。

修复是在每次提交或回滚后在会话上调用.expire_all(); SQLAlchemy 0.5及以上版本会自动执行此操作(autoexpire=True会话,现在称为expire_on_commit我相信),但是对于0.4,您需要注册SessionExtension才能为您执行此操作。< / p>

幸运的是,我们也为此项目使用了collective.lead,所以我的修复就是您的修复:

# The identity map should be flushed on commit.
# SQLAlchemy 0.5 does this properly, but in 0.4 we need to do this via
# a SesssionExtension.

from sqlalchemy import __version__
if __version__[:3] == '0.4':
    from sqlalchemy.orm.session import SessionExtension

    class ExpireAllSessionExtension(SessionExtension):
        def after_commit(self, session):
            """Expire the identity-map on commit"""
            session.expire_all()

        def after_rollback(self, session):
            """Expire the identity-map on rollback"""
            session.expire_all()

    def installExtension():
        # Patch collective.lead.database to let us install the extension
        # on the session created there.
        from collective.lead.database import Database
        old_session = Database.session.fget
        def session(self):
            session = old_session(self)
            if session.extension is None:
                session.extension = ExpireAllSessionExtension()
            return session
        Database.session = property(session)
else:
    def installExtension():
        pass

定义映射器时,使用以下命令安装此扩展名:

from .sessionexpiration import installExtension

# Ensure that sessions get properly expired on commit and rollback.
installExtension()