#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)
Это дает вам немного более чистый код, но это может быть не быстрее.