大量调用 SQLAlchemy 的 InstanceState 类的 expire 方法

Posted

技术标签:

【中文标题】大量调用 SQLAlchemy 的 InstanceState 类的 expire 方法【英文标题】:LOTS of call to the expire method of the SQLAlchemy's InstanceState class 【发布时间】:2013-12-13 20:46:46 【问题描述】:

我正在使用 11 个并行进程执行数据处理任务,并且每次计算的结果都使用 SQLAlchemy 的 ORM 记录在 mysql 数据库的 InnoDB 表中。但是,处理时间比预期的要长。如果我分析这些并行进程之一的执行情况,我可以看到大约 30% 的时间花在 InstanceState 类的 expire 方法上,该方法被调用... 292,957,736 次!

计算执行一个包含 17,106 次迭代的循环,每次迭代执行一次提交。在配置文件中,我看到提交方法称为 17,868,这似乎处于良好的数量级(761 补充提交可能来自周围代码的其他部分)。但是,我不清楚该 expire 方法的作用以及为什么应该多次调用它。它是在每次提交时在表的每一行上调用还是什么?看起来有点像,因为如果 17,106^2 == 292,615,236... 这种行为正常吗?在这种情况下如何做得更好,有什么秘诀或建议吗?确切的代码有点复杂[它在__computeForEvent(...) method of this file] 但是,SQLAlchemy 部分在概念上等价于:

for i in range(17106):
    propagations = []
    for i in range(19):
        propagations.append(Propagation(...))
    session.add_all(propagations)
    session.commit()

其中 Propagation 是 Base 子类。 任何关于如何加快速度和避免expire(...)调用爆炸的建议将不胜感激。

【问题讨论】:

【参考方案1】:

292M 调用 expire() 表明当 commit() 被调用时内存中存在这么多对象,事实上这是一个令人难以置信的巨大数字。

消除这些过期调用的一种直接方法是将expire_on_commit 设置为 False:

sess = Session(expire_on_commit=False)

解决这个问题的更微妙的方法是,如果我们这样做了,那就不要保留内存中的所有对象:

for i in range(17106):
    session.add_all([Propagation() for i in range(19)])
    session.commit()

如果 Propagation() 对象的列表在没有引用循环的情况下没有被强引用,假设 cPython 在取消引用时它们将被垃圾收集,并且不会受到 commit() 内的过期调用的影响。

另一种策略可能只是将 commit() 延迟到循环之后,而不是使用 flush() 一次处理每组项目。这样一来,大多数对象将在到达 commit() 时被垃圾回收。

不过,expire_on_commit 仍然是解决此问题的最直接方法。

【讨论】:

我没有测试你所有的建议,但我最终重构了代码,只在主代码中使用了刷新。我将提交/回滚逻辑移到了“调用者”级别。此更改提供了对数据库状态的更好控制(即避免我的特定应用程序的状态不一致)并修复了我的过期问题。

以上是关于大量调用 SQLAlchemy 的 InstanceState 类的 expire 方法的主要内容,如果未能解决你的问题,请参考以下文章

Python SQLAlchemy基本操作和常用技巧包含大量实例,非常好python

flask_sqlalchemy 调用存储过程

Redshift:DateDiff 调用上的 SqlAlchemy 错误

由于查询调用的自动刷新导致 SQLAlchemy OperationalError

Python之SqlAlchemy

SQLAlchemy的使用