我正在对数据库中的所有行运行批处理操作。这包括选择每个模型并对其做一些事情。将它分成块并将其分块大块是有意义的。
我目前正在使用Paginator,因为它很方便。这意味着我需要对值进行排序,以便可以按顺序分页。这会产生具有order
和limit
子句的SQL语句,对于每个块,我认为Postgres可能会对整个表进行排序(尽管我不能声称对内部有任何了解)。我所知道的是,数据库的CPU占用率约为50%,而且我觉得只有select
这样做太高了。
以RDMBS / CPU友好的方式迭代整个表的最佳方法是什么?
假设在批处理操作期间数据库的内容没有改变。
答案 0 :(得分:5)
根据您的说明,您实际上并不关心您处理的行的排序顺序。如果你的表中有主键(我期望!),这种粗略的分区方法将快得多:
SELECT * FROM tbl WHERE id BETWEEN 0 AND 1000;
SELECT * FROM tbl WHERE id BETWEEN 1001 AND 2000;
...
对于任何大小的表,它对任何偏移执行相同的操作,并且(几乎)相同。 检索主键的最小值和最大值并相应地进行分区:
SELECT min(id), max(id) from tbl; -- then divide in suitable chunks
相反:
SELECT * FROM tbl ORDER BY id LIMIT 1000;
SELECT * FROM tbl ORDER BY id LIMIT 1000 OFFSET 1000;
...
这通常较慢,因为所有行都必须进行排序,并且性能会因较高的偏移量和较大的表而降低。
答案 1 :(得分:3)
以下代码为Django QuerySet实现了Erwin对上面的回答(使用BETWEEN
):
为任意Django QuerySet执行此操作的实用程序函数如下所示。它默认假设'id'是用于between
子句的合适字段。
def chunked_queryset(qs, batch_size, index='id'):
"""
Yields a queryset split into batches of maximum size 'batch_size'.
Any ordering on the queryset is discarded.
"""
qs = qs.order_by() # clear ordering
min_max = qs.aggregate(min=models.Min(index), max=models.Max(index))
min_id, max_id = min_max['min'], min_max['max']
for i in range(min_id, max_id + 1, batch_size):
filter_args = {'{0}__range'.format(index): (i, i + batch_size - 1)}
yield qs.filter(**filter_args)
它会像这样使用:
for chunk in chunked_queryset(SomeModel.objects.all(), 20):
# `chunk` is a queryset
for item in chunk:
# `item` is a SomeModel instance
pass