虽然 Django ORM 提供了一个“惰性”查询集,但我一直在寻找的是一个 generator,它可以为我提供一种惰性获取对象的方法。 django 中的查询集并不是真正的惰性,它们在您尝试访问它们之前是惰性的,数据库将在其中访问并获取 1M 条目。 SQLAlchemy 也是如此。如果你有 oracle 或 postgre 数据库,你很幸运,你可以使用受支持的服务器端游标。如果您使用 mysqldb 或 pymysql 方言,SQLAlchemy 还支持这些以及 mysql。我不确定服务器端游标是如何在幕后工作的。
更多信息
因此,如果您不符合上述任何一种情况,您必须想办法懒惰地获取这些对象。因为 Django ORM 和 SQLAlchemy 都通过将其转换为纯 SQL 查询来支持切片,所以我想我可以使用自定义生成器来切片我需要的批量查询。
免责声明:该解决方案试图解决在本地转储大量数据的问题,它不会尝试最大限度地提高查询性能或与数据库相关的任何性能。
警告:与简单的 Mymodel.objects.all() 相比,这将导致对数据库的更多查询,但对 RAM 的挑战会更少。
def lazy_bulk_fetch(max_obj, max_count, fetch_func, start=0):
counter = start
while counter < max_count:
yield fetch_func()[counter:counter + max_obj]
counter += max_obj
然后以它为例:
fetcher = lazy_bulk_fetch(50, Mymodel.objects.count(), lambda: Mymodel.objects.order_by('id'))
for batch in fetcher:
make_actions(batch)
这将为每次迭代获取一个包含 50 个对象的列表,直到我达到我想要的最大计数。如果您在 django 中将 make_actions(batch) 更改为 print(batch.query),您将看到如下内容:
SELECT "services_service"."id" FROM "services_service" LIMIT 50
SELECT "services_service"."id" FROM "services_service" LIMIT 50 OFFSET 50
SELECT "services_service"."id" FROM "services_service" LIMIT 50 OFFSET 100
SELECT "services_service"."id" FROM "services_service" LIMIT 50 OFFSET 150
slice 和 SQLAlchemy supports 可以使用相同的概念。在这种情况下,解决方案是相同的,但您可以使用 SQLAlchemy 查询对象的 slice 函数来代替 python 切片
编辑:据我所知,SQLAlchemy Query 类实现了__getitem__ 函数。 所以对于 SQLAlchemy,你可以使用我为 Django 建议的完全相同的函数。如果你想明确地使用 slice 函数,你最终会得到如下的结果:
def lazy_bulk_fetch(max_obj, max_count, fetch_func, start=0):
counter = start
while counter < max_count:
yield fetch_func().slice(counter, counter + max_obj)
counter += max_obj
无论如何你都会这样称呼它:
from sqlalchemy import func
fetcher = lazy_bulk_fetch(50, session.query(func.count(Mymodel.id)),
lambda: session.query(Mymodel).order_by(Mymodel.id))
这里有两个注意事项:
- 您想使用
func.count 以便将其转换为服务器中的COUNT SQL 语句。如果您使用len(session.query(Mymodel)),您将在本地转储所有内容,找到它的长度然后将其丢弃
-
我使用lambda,这样实现就像django 一样。我也可以有
lazy_bulk_fetch(50, session.query(func.count(Mymodel.id)),
session.query(Mymodel).order_by(Mymodel.id))
但是我必须在我的功能中拥有
yield fetch_func.slice(counter, counter + max_obj)
编辑#2:我添加了排序,否则您无法确定在第 N 次运行中不会得到相同的结果。订购保证您将获得独特的结果。最好将 id 作为排序键,否则你不能确定你错过了一个结果(因为在第 N 次命中期间,可能已经添加了一个新条目,并且没有 id 的排序可能会导致你错过它或得到双重条目)