【问题标题】:Weird bug happening with Django and MySQLDjango 和 MySQL 发生的奇怪错误
【发布时间】:2019-08-02 17:32:52
【问题描述】:

我有两段代码,都做同样的事情,但一个需要 50 秒,另一个需要不到 5 秒。

模型

class Device(models.Model):
    device_uid = models.CharField(max_length=50, unique=True, null=False)


class DeviceReadings(models.Model):
    device = models.ForeignKey(Device)
    value = models.FloatField(default=0)
    created_dt = models.DateTimeField()

    class Meta:
        unique_together = ('created_dt', 'device')

DeviceReadings 表包含大约 2 亿行。

如果我这样做,mysql 查询将不使用索引,将扫描 2200 万行,需要 40 秒。

#'D1,D2,D3' are comma separated device_uid's
my_devices = "D1,D2,D3".split(",")
devices = Device.objects.filter(device_uid__in=my_devices)
readings = DeviceReadings.objects.filter(created_dt__gte=start_time, created_dt__lte=end_time, device__in=devices)

但是,如果我这样做,mysql 查询将使用索引,并且只会扫描 100 万行,大约需要 4 秒。

my_devices = "D1,D2,D3".split(",")
my_devices_ob = Device.objects.filter(device_uid__in=my_devices)
devices = []
for device in my_devices_ob:
    devices.append(device)
readings = DeviceReadings.objects.filter(created_dt__gte=start_time, created_dt__lte=end_time, device__in=devices)

如果我打印设备数组,这两个代码都是相同的。有人可以解释这里可能发生的事情吗?

【问题讨论】:

    标签: mysql django django-orm


    【解决方案1】:

    请记住,查询集是惰性的。在您的第一个代码中,Device.objects.filter 在您定义它时并未执行。由于您在另一个查询中立即使用它,Django 将其转换为以下形式的子查询:

    SELECT * FROM device_readings WHERE device_id IN (SELECT id FROM devices WHERE ...);
    

    而在第二个查询中,您显式执行了第二个查询,所以 Django 会这样做:

    SELECT * FROM device_readings WHERE device_id IN ("device_id_1", "device_id_2"...);
    

    通常,第一个查询实际上性能更高,因为您不需要单独获取设备数据。您应该调查为什么 EXPLAIN 不是这种情况。

    【讨论】:

    • 嘿,感谢您的快速回复,我忘记了查询集的惰性。我查看了生成的原始查询,在较慢的代码中,它遵循 where 子句 "(devicereadings.device_id) IN (SELECT U0.id FROM core_device U0 WHERE U0.device_uid IN (D1, D2 , D3))" 和更快的 "(devicereadings.device_id) IN (1, 2, 3)" 子查询是 mysql 在第一个不使用索引的原因吗?
    【解决方案2】:

    两个查询应该具有几乎相同的性能,第二个查询的损失对于大型表可以忽略不计。所以你的结果很不寻常;你能始终如一地复制它们吗?

    我想知道子查询是否使 MySQL 改变了评估条件的顺序,在第二个查询中首先过滤日期。如果向created_dt 添加索引会加快第二个查询的速度,情况似乎就是这样:

    created_dt = models.DateTimeField(db_index=True)
    

    我也很好奇以下内容的比较:

    my_devices = "D1,D2,D3".split(",")
    readings = DeviceReadings.objects.filter(
        created_dt__gte=start_time, 
        created_dt__lte=end_time,
        device__uid__in=my_devices)
    

    它为您提供更简洁的代码,但可能不会更快。

    【讨论】:

      猜你喜欢
      • 2016-03-15
      • 2010-12-24
      • 1970-01-01
      • 1970-01-01
      • 2013-02-27
      • 1970-01-01
      • 2011-10-13
      • 2014-05-27
      • 2019-03-31
      相关资源
      最近更新 更多