【问题标题】:fast lookup for the last element in a Django QuerySet?快速查找 Django QuerySet 中的最后一个元素?
【发布时间】:2010-12-23 14:09:32
【问题描述】:

我有一个名为 Valor 的模型。 Valor 有一个机器人。我这样查询:

Valor.objects.filter(robot=r).reverse()[0]

获得最后的Valor the r 机器人。 Valor.objects.filter(robot=r).count() 大约是 200000,在我的 PC 中获取最后一个项目大约需要 4 秒。

如何加快速度?我查询的方式不对?

【问题讨论】:

  • 你有一个非常华丽的 ForeignKey、OneToOneField 或 ManyToManyField 关系结构吗?
  • fwiw,这很慢,因为您正在选择 valor 表中的所有内容,当您将每个条目转换为列表时(通过 .reverse)为每个条目实例化一个 django 模型实例,并采取只有列表中的第一项。

标签: django optimization django-queryset


【解决方案1】:

解决这个问题的最佳 mysql 语法如下:

SELECT * FROM table WHERE x=y ORDER BY z DESC LIMIT 1

django 相当于:

Valor.objects.filter(robot=r).order_by('-id')[:1][0]

请注意此解决方案如何利用 django 的 slicing 方法在编译对象列表之前限制查询集。

【讨论】:

    【解决方案2】:

    如果之前的建议都不起作用,我建议将 Django 排除在外,并针对您的数据库运行此原始 sql。我猜你的表名,所以你可能需要相应地调整:

    SELECT * FROM valor v WHERE v.robot_id = [robot_id] ORDER BY id DESC LIMIT 1;
    

    这么慢吗?如果是这样,请让您的 RDBMS(MySQL?)向您解释查询计划。这将告诉您它是否正在执行任何全表扫描,您显然不希望使用那么大的表。您还可以编辑您的问题并包含 valor 表的架构供我们查看。

    此外,您可以通过执行此操作(使用 Peter Rowell 提供的查询集)查看 Django 生成的 SQL:

    qs = Valor.objects.filter(robot=r).order_by('-id')[0]
    print qs.query
    

    确保 SQL 类似于我在上面发布的“原始”查询。您还可以让您的 RDBMS 向您解释该查询计划。

    【讨论】:

    • qs.query(或 .query.as_sql())绝对是任何试图从 DB 端追查此问题的人的方法。想知道如何在低级别处理切片/sql 限制、order_by() 和 filter() 的人应该尝试一下。
    【解决方案3】:

    听起来您的数据集已经足够大了,您可能想要对事物进行一些非规范化处理。您是否尝试过跟踪 Robot 对象中的最后一个 Valor 对象?

    class Robot(models.Model):
        # ...
        last_valor = models.ForeignKey('Valor', null=True, blank=True)
    

    然后使用post_save signal 进行更新。

    from django.db.models.signals import post_save
    
    def record_last_valor(sender, **kwargs):
        if kwargs.get('created', False):
            instance = kwargs.get('instance')
            instance.robot.last_valor = instance
    
    post_save.connect(record_last_valor, sender=Valor)
    

    您将在创建 Valor 对象时支付额外的数据库事务的成本,但 last_valor 查找速度会非常快。试一试,看看这种折衷对您的应用来说是否值得。

    【讨论】:

    • 这是设计问题的一个很好的解决方案,但它留下了一个问题,即为什么他的设计表现不佳,即使给出的查询比他原来的更合适。通过适当的索引、排序和限制,他最初的规范化设计也应该“非常快”。我希望 OP 用他的发现更新这个问题。
    • Django 让我把它改成:def record_last_valor(sender, **kwargs): if created: instance.robot.last_valor = instance
    • 我同意乔,这并没有试图回答潜在的数据库性能问题。似乎已经有很多关于检查索引的讨论,而您对 qs.query 的建议似乎是调查检查索引方法的最佳方法。
    • 我已经尝试过这种方法,但无法获得保存在数据库中的值。
    • 您确定正在调用 record_last_valor 吗?尝试将print locals()import pdb; pdb.set_trace() 添加到record_last_valor。或者我的示例代码可能需要一个 instance.robot.save()。
    【解决方案4】:

    嗯,没有 order_by 子句,所以我想知道你所说的“最后一个”是什么意思。假设您的意思是“最后添加”,

    Valor.objects.filter(robot=r).order_by('-id')[0]
    

    可能会为您完成这项工作。

    【讨论】:

    • 在 Valor 模型中我有 Meta: ordering = ('id',)。这是否使您的查询与我的相同?
    • 你的 valor 表中是否有适当的 robots_id 索引?
    【解决方案5】:

    django 1.6 引入了 .first() 和 .last():

    https://docs.djangoproject.com/en/1.6/ref/models/querysets/#last

    所以你可以这样做:

    Valor.objects.filter(robot=r).last()
    

    【讨论】:

      【解决方案6】:

      也应该相当快:

      qs = Valor.objects.filter(robot=r) # <-- it doesn't hit the database
      count = qs.count()                 # <-- first hit the database, compute a count
      last_item = qs[ count-1 ]          # <-- second hit the database, get specified rownum
      

      因此,实际上您只执行 2 个 SQL 查询;)

      【讨论】:

        【解决方案7】:

        django 中有限制子句吗?这样你就可以拥有数据库,只需返回一条记录。

        mysql

         select * from table where x = y limit 1
        

        sql 服务器

         select top 1 * from table where x = y
        

        神谕

         select * from table where x = y and rownum = 1
        

        我知道这没有翻译成 django,但有人可以回来清理它。

        【讨论】:

        • 限制和反向,你的意思。对吗?
        • 在 Django 中,您使用 Python 的切片语法进行限制。 Valor.objects.filter(robot=r).reverse()[0:1].get()
        • 我刚刚尝试了最后一个查询,它和我原来的查询一样慢。
        【解决方案8】:

        这样做的正确方法是使用内置的 QuerySet 方法 latest() 并将其提供给它应该排序的任何列(字段名称)。缺点是只能按单个db列排序。

        当前的实现看起来像这样,并且在与@Aaron 的建议相同的意义上进行了优化。

        def latest(self, field_name=None):
            """
            Returns the latest object, according to the model's 'get_latest_by'
            option or optional given field_name.
            """
            latest_by = field_name or self.model._meta.get_latest_by
            assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
            assert self.query.can_filter(), \
                    "Cannot change a query once a slice has been taken."
            obj = self._clone()
            obj.query.set_limits(high=1)
            obj.query.clear_ordering()
            obj.query.add_ordering('-%s' % latest_by)
            return obj.get()
        

        【讨论】:

          【解决方案9】:
          Model_Name.objects.first()
          

          //获取第一个元素

          Model_name.objects.last()
          

          //获取last()

          在我的情况下最后一个不起作用,因为数据库中只有一行 也可能对你有帮助:)

          【讨论】:

            猜你喜欢
            • 2011-11-07
            • 2022-01-21
            • 2012-05-07
            • 1970-01-01
            • 2011-04-07
            • 1970-01-01
            • 2014-05-07
            • 1970-01-01
            • 2012-05-07
            相关资源
            最近更新 更多