Странная ошибка, происходящая с Django и MySQL - PullRequest
0 голосов
/ 12 марта 2019

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

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

Ответы [ 2 ]

0 голосов
/ 12 марта 2019

Оба запроса должны иметь практически одинаковую производительность с штрафом за второй запрос, который пренебрежимо мал для больших таблиц.Так что ваши результаты довольно необычны;Вы можете последовательно копировать их?

Мне интересно, заставляет ли 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)

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

0 голосов
/ 12 марта 2019

Помните, что наборы запросов ленивы.В вашем первом коде 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.

...