使用Django QuerySet以块的形式处理数据库的最佳方法?

Joe*_*Joe 5 django postgresql django-models database-optimization

我正在对数据库中的所有行运行批处理操作.这包括选择每个模型并对其做一些事情.将它分成块并将其分块大块是有意义的.

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

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

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

Erw*_*ter 5

从您的描述中,您实际上并不关心您处理的行的排序顺序.如果你的表中有主键(我期望!),这种粗略的分区方法会快得多:

SELECT * FROM tbl WHERE id BETWEEN 0    AND 1000;
SELECT * FROM tbl WHERE id BETWEEN 1001 AND 2000;
...
Run Code Online (Sandbox Code Playgroud)

对于任何大小的表,这对任何偏移执行相同的操作,并且(几乎)相同.检索主键的最小值和最大值并相应地进行分区:

SELECT min(id), max(id) from tbl; -- then divide in suitable chunks
Run Code Online (Sandbox Code Playgroud)

相反:

SELECT * FROM tbl ORDER BY id LIMIT 1000;
SELECT * FROM tbl ORDER BY id LIMIT 1000 OFFSET 1000;
...
Run Code Online (Sandbox Code Playgroud)

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


spo*_*key 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)
Run Code Online (Sandbox Code Playgroud)

它将像这样使用:

for chunk in chunked_queryset(SomeModel.objects.all(), 20):
    # `chunk` is a queryset
    for item in chunk:
        # `item` is a SomeModel instance
        pass
Run Code Online (Sandbox Code Playgroud)

您还可以更改接口,以便不需要额外的嵌套循环,但可以这样做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
Run Code Online (Sandbox Code Playgroud)