6

我正在对数据库中的所有行运行批处理操作。这涉及选择每个模型并对其进行处理。将其拆分为块并逐块执行是有意义的。

我目前正在使用 Paginator,因为它很方便。这意味着我需要对这些值进行排序,以便可以按顺序对它们进行分页。这确实会生成具有orderlimit子句的 SQL 语句,并且对于每个块,我认为 Postgres 可能正在对整个表进行排序(尽管我不能声称对内部有任何了解)。我所知道的是数据库的 CPU 大约为 50%,我认为这太高了,不能做selects。

以 RDMBS/CPU 友好的方式迭代整个表的最佳方法是什么?

假设在批处理操作期间数据库的内容没有改变。

4

2 回答 2

6

根据您的描述,您实际上并不关心您处理的行的排序顺序。如果您的表中有主键(我希望如此!),那么这种粗略的分区方法会快得多

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;
...

这通常较慢,因为必须对所有行进行排序,并且性能会随着更高的偏移量和更大的表而降低。

于 2012-01-03T03:24:00.260 回答
5

以下代码BETWEEN为 Django QuerySet 实现了 Erwin 的上述答案(使用 ):

将为任意 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

您还可以更改界面,以便不需要额外的嵌套循环,但可以这样做for item in chunked_queryset(qs)

def chunked_queryset(qs, batch_size, index='id'):
    """
    Yields a queryset that will be evaluated in batches
    """
    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)}
        for item in qs.filter(**filter_args):
            yield item
于 2016-09-17T18:54:31.347 回答