我几天前发布了一个关于Python脚本占用大量内存的问题Understanding Python memory usage
经过一些试验和错误后,我设法将内存使用量的增长本地化为一个执行数据库清理的函数。我的简化架构如下(orm):
class A(Base):
id = Column(Integer, primary_key=True)
foo = Column(String)
bar = Column(DateTime)
replication_confirmed = Column(Boolean, default=False)
class B(Base):
id = Column(Integer, primary_key=True)
xyzzy = Column(Integer)
barf = Column(String)
replication_confirmed = Column(Boolean, default=False)
aref = Column(Integer, ForeignKey("A.id"))
为简单起见,此脚本充当计算机之间的一种复制服务器。另一个程序向此发送RabbitMQ消息,请求更新并获取更新以及json消息中A和B的新行。还有其他一些实际修改A和B的进程,每次更新时,它们都会将复制确认为False,并且每次将数据发送到" main"服务器,此标志更改为True。
为了避免在这个纯粹的过渡数据库中出现混乱,我偶尔进行清理,这就是内存增长发生的地方。我打算做的是从两个表中删除replication_confirmed = True
的所有行,同时保持完整性,因为复制可能在表之间不同步。
这是我的删除代码(简化,它确实有错误处理等)。它可以工作,但我认为它现在从上次清理后大量增加的表中将大量内容加载到内存中,这会导致进程请求大量内存,然后它就永远不会释放。由于有许多进程在运行相同的表,因此我使用with_for_update()
来锁定要受影响的行,并防止编写器进程在执行清理时修改这些表。
_todc = session.query(A).filter(A.replication_confirmed == True).\
with_for_update().all()
_aids = [i.id for i in _todc]
_todb = session.query(B).filter(B.aref in_(_aids),
B.replication_confirmed == True).all()
for _tb in _todb:
_session.delete(_tb)
_remaining = session.query(B).all()
_remaining_ids = [i.id for i in _remaining]
_adelete = session.query(A).filter(A.replication_confirmed == True,
A.id.notin_(_remaining_ids)).all()
for _ta in _adelete:
session.delete(_ta)
session.commit()
这一切都有效,但必须有更好的方法。由于复制过程的性质,表可能在复制方面不完全同步。可能已复制B中的行,而来自A的引用行仍处于待处理状态。或相反亦然。
这样做的效率更高?这现在可以读取从受影响的表到内存的所有内容,但我可能在数据库级别上完成所有操作而根本不处理内存中的数据。我只是不知道该怎么做。
有什么想法吗?这是Python 2.7,Sqlalchemy和postgresql9.5。
答案 0 :(得分:1)
如果我已经正确理解了你,那么2个带连接的删除应该是:
In [11]: session.query(B).\
...: filter(B.replication_confirmed,
...: session.query(A).
...: filter(A.replication_confirmed,
...: A.id == B.aref).
...: exists()).\
...: delete(synchronize_session=False)
接着是
In [12]: session.query(A).\
...: filter(A.replication_confirmed,
...: ~session.query(B).
...: filter(B.aref == A.id).
...: exists()).\
...: delete(synchronize_session=False)
虽然A中的行尚未锁定,但根据您的隔离级别,可能会在2个查询之间发生变化。