【问题标题】:Prefetch query with relational conditions - Django具有关系条件的预取查询 - Django
【发布时间】:2022-01-21 15:32:03
【问题描述】:

我正在尝试发出一个预取所有数据的请求,以减少对我的数据库的请求量。我使用prefetch_related() 方法和Prefetch 对象类来告诉在同一个请求中加载什么。

代码:

pairs_queryset = Pair.objects.filter(quote_id="2781", last_price__isnull=False) \
                 | Pair.objects.filter(quote_id="825", last_price__isnull=False) \
                 | Pair.objects.filter(quote_id="3408", last_price__isnull=False)

queryset = Portfolio.objects.prefetch_related(
    Prefetch('connections__wallets__currency__metadata__images'),
    Prefetch('connections__wallets__currency__base_pairs', queryset=pairs_queryset),
).get(id=portfolio_id)

data = Serializer(queryset).data

型号:

class Connection(models.Model):
    exchange = models.ForeignKey(Exchange, related_name='exchange', on_delete=models.CASCADE)

    portfolios = models.ManyToManyField(Portfolio, related_name='connections')

    # ...

class Pair(models.Model):
    id = models.IntegerField(primary_key=True)  # CoinMarketCap id

    exchange = models.ForeignKey(Exchange, related_name='pairs', on_delete=models.CASCADE)

    # ...

我的数据库中有多个base_pairs 与Exchange 有关系(见上文)。

我的问题: 我想向预取相关对象添加一个过滤器,该过滤器使用包含在预取的连接(即exchange_id)中的值,如下所示:

connections__wallets__currency__base_pairs
     ^                               |
     |_______________________________|
          depends on the current 
    connection's exchange_id attribute 

由于connections 是一个集合(多个值),我想要以下内容:connections__wallets__currency__base_pair 检索与当前获取的connection 相关的base_pairs。

我阅读了文档,但找不到任何解决方案。使用预取大大减少了请求时间(约 5 秒而不是约 3 分钟)。

另一种方式:

# get portfolio + prefetch connections (and other things...)
    portfolio = Portfolio.objects.prefetch_related(
        'connections__wallets__currency__metadata__images'
    ).get(id=portfolio_id)

    for connection in portfolio.connections.all():
        # build filter
        pairs_queryset = ExchangePair.objects.filter(
            exchange_id=connection.exchange_id,
            quote_id__in=["825", "2781", "3408", "4687"],
            last_price__isnull=False,
        )
        # prefetch for related connections
        prefetch_related_objects(
            [connection],
            Prefetch('wallets__currency__base_pairs', queryset=pairs_queryset),
        )

在这里,Django 正在重用来自循环的先前连接查询的缓存。所以它非常快。但是,缓存的 base_pairs 在结果中混杂在一起,因为 Django 忘记了我传递给 Prefetch() 实例的过滤器查询集。显然,这似乎是 Django 的一个错误。所以我正在寻找另一种方法来实现这一目标。

【问题讨论】:

    标签: python django django-models django-rest-framework django-views


    【解决方案1】:

    我不知道你问的究竟是否可行,但你可以将预取阶段分为三个步骤:

    # get portfolio + prefetch connections (and other things...)
    portfolio = Portfolio.objects.prefetch_related(
        'connections__wallets__currency__metadata__images'
    ).get(id=portfolio_id)
    
    # build filter
    pairs_queryset = Pair.objects.filter(
        quote_id__in=("2781","825","3408"),
        last_price__isnull=False,
        connection__zzz__in = set(_.zzz for _ in portfolio.connections.all())
    )
    
    # prefetch for related connections
    prefetch_related_objects(
        portfolio.connections.all(),
        Prefetch('wallets__currency__base_pairs', queryset=pairs_queryset
    )
    data = Serializer(portfolio).data
    

    【讨论】:

    • 好的,检查您的问题编辑建议并批准最后一个。我修改了代码,这似乎工作。在验证您的答案之前,我将进行一些测试。谢谢。
    • @PaulBénéteau 您的编辑将进入审核队列,该队列由具有足够声誉的用户进行审核。作者可以批准他们,但在你的情况下,有两个人拒绝了你的提议(包括我)。您的编辑对帖子进行了太多更改。随意发布您调整后的解决方案作为新答案。您可以通过链接到原始答案来“致敬”原始答案。另请注意,如果它帮助您解决问题,您可以投票赞成这个答案
    • @Nielk 获取的对必须依赖于 connection.exchange_id。所以我在portfolio.connections.all()中为连接做了这个`:pairs_queryset = ExchangePair.objects.filter(exchange_id=connection.exchange_id, quote_id__in=["825", "2781", "3408", "4687"], last_price__isnull=False, ) prefetch_related_objects( [connection], Prefetch('wallets__currency__base_pairs', queryset=pairs_queryset) )` 但由于任何我无法解释的原因,它只在 90% 的时间内有效。
    • @PaulBénéteau 首先,您如何检查“它有效”?如果您删除exchange_id 上的过滤器,它会起作用吗?遍历每个连接的效率较低(= 您将生成更多查询)。退后一步,如果您只想有效地获取与您的投资组合相关的所有货币对,也许还有其他策略 - 这取决于您的数据集(每个表中的基数,不同货币之间共享多少货币对,.. .))
    • @Nielk 从另一个 exchange_id 获取 base_pairs 时它不起作用。我唯一能做的就是通过在每个循环开始时执行 connection.refresh_from_db() 来删除缓存。因为 Django 滥用缓存并混合了之前获取的所有 base_pairs,即使它不符合 queryset=pairs_queryset 过滤器...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-03
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2012-09-06
    • 2017-10-05
    • 1970-01-01
    相关资源
    最近更新 更多