一尘不染

内存有效的内置SqlAlchemy迭代器/生成器?

mysql

我有一个〜10M记录的MySQL表,可以使用SqlAlchemy进行交互。我发现对这个表的大子集的查询将消耗过多的内存,即使我以为我使用的是内置生成器,它可以智能地获取数据集的一口大小的块:

for thing in session.query(Things):
    analyze(thing)

为了避免这种情况,我发现我必须构建自己的迭代器,该迭代器会分块地进行处理:

lastThingID = None
while True:
    things = query.filter(Thing.id < lastThingID).limit(querySize).all()
    if not rows or len(rows) == 0: 
        break
    for thing in things:
        lastThingID = row.id
        analyze(thing)

这是正常的还是关于SA内置发电机我缺少什么?

这个问题的答案似乎表明内存消耗是不希望的。


阅读 460

收藏
2020-05-17

共1个答案

一尘不染

大多数DBAPI实现在获取行时都会完全缓冲行-因此通常,在SQLAlchemy ORM甚至没有保留一个结果之前,整个结果集就在内存中。

但是,有效的方法Query是在返回对象之前默认情况下完全加载给定的结果集。这里的基本原理涉及的查询不仅仅是简单的SELECT语句。例如,在连接到可能在一个结果集中多次返回相同对象标识的其他表中(与急切加载相同),完整的行集需要存储在内存中,以便可以返回正确的结果,否则返回集合。可能只是部分填充。

因此Query提供了通过更改此行为的选项yield_per()。此调用将导致Query批量生成行,并在其中指定批量大小。正如文档所述,这仅在您不进行任何急切加载集合的情况下才是合适的,因此基本上是您真的知道自己在做什么。同样,如果底层的DBAPI预缓冲行,则仍然会有内存开销,因此该方法的伸缩性仅比不使用它更好。

我很少用过yield_per();
相反,我使用了您上面建议的使用窗口函数的LIMIT方法的更好版本。LIMIT和OFFSET存在一个巨大的问题,即非常大的OFFSET值会导致查询变得越来越慢,因为N的OFFSET会使查询分页N行-
就像执行相同的查询而不是一次,每次查询50次行数越来越大。使用窗口函数方法,我预取了一组“窗口”值,这些值引用了我要选择的表的块。然后,我发出单独的SELECT语句,每个语句一次从这些窗口之一中提取。

窗口函数方法在Wiki上,我使用它非常成功。

另请注意:并非所有数据库都支持窗口功能。您需要Postgresql,Oracle或SQL Server。恕我直言,至少使用Postgresql绝对值得-
如果您使用的是关系数据库,则最好使用最佳数据库。

2020-05-17