内存高效的内置SqlAlchemy迭代器/生成器?
我有一个〜10MloggingMySQL表,我使用SqlAlchemy接口。 我发现在这个表的大子集上查询会消耗太多的内存,即使我以为我使用了一个内置的生成器,可以智能地获取数据集的一小块数据块:
for thing in session.query(Things): analyze(thing)
为了避免这种情况,我发现我必须构build自己的迭代器,
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内置发电机?
这个问题的答案似乎表明,内存消耗是不可预料的。
大多数DBAPI实现在获取行时充分缓冲行 – 通常,在SQLAlchemy ORM甚至获得一个结果之前,整个结果集在内存中。
但是,查询的工作方式是,在返回给对象之前,默认情况下会完全加载给定的结果集。 这里的基本原理是针对不仅仅是简单的SELECT语句的查询 – 连接到可能在一个结果集中多次返回相同的对象标识的其他表(在急切加载中通用),整套行需要在内存中可以返回正确的结果 – 否则集合等可能只是部分填充。
所以Query提供了一个选项来改变这种行为,即yield_per()调用http://www.sqlalchemy.org/docs/orm/query.html?highlight=yield_per#sqlalchemy.orm.query.Query.yield_per 。 这个调用将导致查询产生成批的行,你给它的批量大小。 正如文档所述,这只有在您不需要加载任何集合的情况下才适用 – 所以基本上,如果您确实知道自己在做什么。 而且,如果底层的DBAPI预先caching了行,那么仍然会有这样的内存开销,所以这种方法只比没有使用它稍微好点。
我很less使用yield_per() – 相反,我使用了上面使用窗口函数build议的LIMIT方法的更好的版本。 LIMIT和OFFSET有一个很大的问题,即非常大的OFFSET值导致查询变得越来越慢,因为N的OFFSET导致它遍历N行 – 这就像执行相同的查询50次而不是1次,每次读取越来越多的行。 使用窗口函数方法,我预先获取一组“窗口”值,这些值指向要select的表的块。 然后我发出单独的SELECT语句,每次从这些窗口中的一个拉一次。
窗口函数的方法是在维基上http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowedRangeQuery ,我用它很成功。
另请注意,并非所有数据库都支持窗口function – 您需要PG,Oracle或SQL Server。 恕我直言,至less使用Postgresql绝对是值得的 – 如果你使用的是关系型数据库,那么最好使用它。
我一直在寻找与SQLAlchemy有效的遍历/分页,并希望更新这个答案。
我认为你可以使用slice调用来正确限制查询的范围,并且可以高效地重用它。
例:
window_size = 10 # or whatever limit you like window_idx = 0 while True: start,stop = window_size*window_idx, window_size*(window_idx+1) things = query.slice(start, stop).all() if things is None: break for thing in things: analyze(thing) if len(things) < window_size: break window_idx += 1
本着Joel的回答精神,我使用以下内容:
WINDOW_SIZE = 1000 def qgen(query): start = 0 while True: stop = start + WINDOW_SIZE things = query.slice(start, stop).all() if things is None: break for thing in things: yield(thing) start += WINDOW_SIZE
AFAIK,第一个变体仍然从表中获取所有元组(使用一个SQL查询),但是在迭代时为每个实体构buildORM表示。 所以它比迭代之前build立所有实体列表更有效率,但是你仍然需要将所有(原始)数据提取到内存中。
因此,在巨大的桌子上使用LIMIT听起来像是个好主意。
使用LIMIT / OFFSET是不好的,因为你需要先find所有的{OFFSET}列,所以更大的是OFFSET – 你得到的更长的请求。 对于我来说,使用窗口化查询也会在大型数据表(大量数据等待第一个结果的时间太长,对于分块的Web响应不太好)上给出不好的结果。
这里给出的最佳方法是https://stackoverflow.com/a/27169302/450103 。 在我的情况下,我解决了问题,只需使用date时间字段上的索引,并使用datetime> = previous_datetime获取下一个查询。 愚蠢的,因为我在之前的不同情况下使用该索引,但认为对于提取所有数据窗口查询会更好。 在我的情况下,我错了。