Странная ошибка, происходящая с Django и MySQL

#mysql #django #django-orm

#mysql #django #django-orm

Вопрос:

У меня есть два фрагмента кода, оба делают одно и то же, но один занимает 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 содержит около 200 миллионов строк.

Если я сделаю это, запрос mysql не будет использовать индекс и будет сканировать 22 миллиона строк и займет 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 будет использовать индекс и будет сканировать только 1 миллион строк и займет около 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)
  

Если я напечатаю массив devices, он будет одинаковым в обоих кодах. Кто-нибудь может объяснить, что здесь может происходить?

Ответ №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.

Комментарии:

1. Привет, спасибо за быстрый ответ, я забыл о ленивой природе наборов запросов. Я просмотрел сгенерированный необработанный запрос, и в более медленном коде он содержит следующее предложение where «( devicereadings . device_id ) В (ВЫБЕРИТЕ U0. id ИЗ core_device U0, ГДЕ U0. device_uid В (D1, D2, D3))» и в более быстром «( devicereadings . device_id ) В (1, 2, 3)» Является ли подзапрос причиной, по которой mysql не использует index в первом?

Ответ №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)
  

Это дает вам немного более чистый код, но это может быть не быстрее.