我有一张很大的桌子。它目前在 MySQL 数据库中。我用django。
我需要遍历表的每个元素以预先计算一些特定的数据(也许如果我做得更好,我可以这样做,但这不是重点)。
我想通过不断使用内存来尽可能快地保持迭代。
正如在 *Large* Django QuerySet 中限制内存使用以及为什么迭代大型 Django QuerySet 会消耗大量内存中已经明确指出的那样?,对 django 中所有对象的简单迭代将杀死机器,因为它将从数据库中检索所有对象。
寻求解决方案
首先,为了减少你的内存消耗,你应该确保 DEBUG 是 False (或者猴子补丁游标:在保持 settings.DEBUG 的同时关闭 SQL 日志记录?)以确保 django 没有存储东西connections
进行调试。
但即便如此,
for model in Model.objects.all()
是不行的。
即使是稍微改进的形式:
for model in Model.objects.all().iterator()
Usingiterator()
将通过不在内部存储缓存结果来节省一些内存(尽管不一定在 PostgreSQL 上!);但显然仍会从数据库中检索整个对象。
一个天真的解决方案
第一个问题的解决方案是根据计数器将结果切片 a chunk_size
。有几种编写方法,但基本上它们都归结为OFFSET + LIMIT
SQL 中的查询。
就像是:
qs = Model.objects.all()
counter = 0
count = qs.count()
while counter < count:
for model in qs[counter:counter+count].iterator()
yield model
counter += chunk_size
虽然这是高效的内存(与 成比例的恒定内存使用chunk_size
),但它在速度方面确实很差:随着 OFFSET 的增长,MySQL 和 PostgreSQL(可能还有大多数 DB)都将开始阻塞和减速。
更好的解决方案
Thierry Schellenbach在这篇文章中提供了一个更好的解决方案。它在 PK 上进行过滤,这比偏移快得多(多快可能取决于数据库)
pk = 0
last_pk = qs.order_by('-pk')[0].pk
queryset = qs.order_by('pk')
while pk < last_pk:
for row in qs.filter(pk__gt=pk)[:chunksize]:
pk = row.pk
yield row
gc.collect()
这开始变得令人满意。现在内存 = O(C),速度 ~= O(N)
“更好”解决方案的问题
只有当 PK 在 QuerySet 中可用时,更好的解决方案才有效。不幸的是,情况并非总是如此,特别是当 QuerySet 包含不同 (group_by) 和/或值 (ValueQuerySet) 的组合时。
对于这种情况,不能使用“更好的解决方案”。
我们能做得更好吗?
现在我想知道我们是否可以更快地避免关于没有 PK 的 QuerySets 的问题。也许使用我在其他答案中找到的东西,但仅限于纯 SQL:使用cursors。
由于我对原始 SQL 非常不满意,尤其是在 Django 中,所以真正的问题来了:
我们如何为大表构建更好的 Django QuerySet 迭代器
我从我读过的内容中得出的结论是,我们应该使用服务器端游标(显然(请参阅参考资料)使用标准 Django 游标不会达到相同的结果,因为默认情况下 python-MySQL 和 psycopg 连接器都会缓存结果)。
这真的会是一个更快(和/或更有效)的解决方案吗?
这可以在 django 中使用原始 SQL 来完成吗?还是我们应该根据数据库连接器编写特定的 python 代码?
PostgreSQL和MySQL中的服务器端游标
这就是我目前所能得到的……
一个姜戈chunked_iterator()
现在,当然最好的方法是让这种方法作为queryset.iterator()
,而不是iterate(queryset)
,并成为 django 核心的一部分,或者至少是一个可插入的应用程序。
更新感谢评论中的“T”找到带有一些附加信息的django 票。连接器行为的差异使得最好的解决方案可能是创建一个特定的chunked
方法,而不是透明地扩展iterator
(听起来对我来说是个好方法)。存在一个实现存根,但一年内没有任何工作,而且看起来作者还没有准备好跳上它。
附加参考:
- 为什么 MYSQL 更高的 LIMIT 偏移量会减慢查询速度?
- 如何加快 LIMIT 子句中偏移量较大的 MySQL 查询?
- http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
- postgresql:offset + limit 变得非常慢
- 提高 PostgreSQL 中的 OFFSET 性能
- http://www.depesz.com/2011/05/20/pagination-with-fixed-order/
- 如何在 MySQL 中的 python服务器端游标中获取逐行 MySQL ResultSet
编辑:
Django 1.6 正在添加持久数据库连接
在某些情况下,这应该有助于使用游标。仍然超出了我目前的技能(和学习时间)如何实施这样的解决方案..
此外,“更好的解决方案”绝对不适用于所有情况,不能用作通用方法,只能根据具体情况调整存根......