我有一个~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内置发电机我缺少了什么?

this question的答案似乎表明内存消耗不是预期的.

推荐答案

大多数DBAPI实现在获取行时会完全缓冲它们——因此,通常在SQLAlchemy ORM获取一个结果之前,整个结果集都在内存中.

但是,Query的工作方式是,默认情况下,它会在返回对象之前完全加载给定的结果集.这里的基本原理涉及的查询不仅仅是简单的SELECT语句.例如,在与可能在一个结果集中多次返回同一对象标识的其他表的联接中(与即时加载常见),完整的行集合需要在内存中,以便可以返回正确的结果,否则可能只会部分填充这些集合.

所以Query提供了一个选项,可以通过yield_per()改变这种行为.此调用将导致Query成批生成行,您可以在其中指定批大小.正如docs所说,这只适用于你没有进行任何形式的Collection 加载的情况,因此基本上是如果你真的知道自己在做什么.此外,如果底层DBAPI预先缓冲行,那么仍然会有内存开销,因此这种方法的可扩展性只比不使用它略好.

我几乎从不使用yield_per();取而代之的是,我使用了一种更好的极限方法,您在上面建议使用窗口函数.LIMIT和OFFSET有一个巨大的问题,即非常大的偏移量值会导致查询速度越来越慢,因为偏移量为N会导致它在N行中翻页-这就像是对同一个查询执行50次而不是一次,每次读取越来越多的行.使用窗口函数方法,我预先获取一组"窗口"值,这些值引用我想要 Select 的表的块.然后,我发出单独的SELECT语句,每个语句一次从其中一个窗口中提取.

窗口函数方法是on the wiki,我使用它非常成功.

还要注意:并非所有数据库都支持窗口功能;您需要Postgresql、Oracle或SQL Server.IMHO至少使用Postgresql是绝对值得的——如果你使用的是关系数据库,你最好使用最好的.

Mysql相关问答推荐

正则表达式转换为MYSQL格式

使用复合主键更新后的MySQL触发器失败

从表中动态删除所有空列

如何从mysql中的不可用日期范围获取可用日期范围?

将递归 CTE 结果合并到辅助 SQL Select

如何使用mysql更新列中的json数据

为生日创建 MySQL 索引

如何使用 DBD::mysql 判断 MySQL 服务是否正在运行

使用适配器设计模式和外观设计模式实现

在 SQL 中 for each 组返回具有最大值的行,包括具有相同值的行

从 SQL 中的左连接和内连接中减go 计数

判断一对记录是否属于多个组ID

MySQL:使用来自查询的信息创建一个新表

SQL - 如何找到列中的最高数字?

MySQL - 表'my_table'没有被锁定表锁定

Yii2 如何进行 where AND 或 OR 条件分组?

MySQL导入数据库但忽略特定表

如何通过一个语句描述数据库中的所有表?

将 UTF8 表上的 latin1 字符转换为 UTF8

MySQL - 重复表