2015-10-14 4 views
1

У меня есть приложение Django 1.8, и я использую базу данных MsSQL с pyodbc в качестве бэкэнда db (используя модуль «django-pyodbc-azure»).Ошибка производительности с django exclude

У меня есть следующие модели:

class Branch(models.Model): 
    name = models.CharField(max_length=30) 
    startTime = models.DateTimeField() 

class Device(models.Model): 
    uid = models.CharField(max_length=100, primary_key=True) 
    type = models.CharField(max_length=20) 
    firstSeen = models.DateTimeField() 
    lastSeen = models.DateTimeField() 

class Session(models.Model): 
    device = models.ForeignKey(Device) 
    branch = models.ForeignKey(Branch) 
    start = models.DateTimeField() 
    end = models.DateTimeField(null=True, blank=True) 

мне нужно запросить модель сеанса, и я хочу, чтобы исключить некоторые записи с определенными значениями устройств. Поэтому я выпускаю следующий запрос:

sessionCount = Session.objects.filter(branch=branch) 
          .exclude(device__in=badDevices)            
          .filter(end__gte=F('start')+timedelta(minutes=30)).count() 

badDevices - это предварительно заполненный список идентификаторов устройств, насчитывающий около 60 наименований.

badDevices = ['id-1', 'id-2', ...] 

Этот запрос занимает около 1,5 секунд. Если я удалю исключение из запроса, он займет около 250 миллисекунд.

Я напечатал сгенерированный sql для этого набора запросов и попробовал его в моем клиенте базы данных. Там обе версии выполнены примерно за 250 миллисекунд.

Это сгенерированный SQL:

SELECT [session].[id], [session].[device_id], [session].[branch_id], [session].[start], [session].[end] 
FROM [session] 
WHERE ([session].[branch_id] = my-branch-id AND 
NOT ([session].[device_id] IN ('id-1', 'id-2', 'id-3',...)) AND 
DATEPART(dw, [session].[start]) = 1 
AND [session].[end] IS NOT NULL AND 
[session].[end] >= ((DATEADD(second, 600, CAST([session].[start] AS datetime))))) 

Таким образом, с помощью исключения в уровне базы данных не кажется, не влияет на производительность запросов, но в Джанго, запрос выполняется в 6 раз медленнее, если я добавляю исключить часть. Что может быть причиной этого?

+0

Возможно, вам будет полезно указать, какой БД вы используете. (т. е. реализация pyodbc или Django-mssql) – Ringil

+0

@Ringil благодарит за предложение, обновил вопрос с помощью этой информации. –

ответ

2

Общая проблема заключается в том, что django делает дополнительную работу для подготовки предложения exclude. После этого шага и к тому времени, когда SQL был сгенерирован и отправлен в базу данных, на стороне django ничего интересного не происходит, что может вызвать такую ​​значительную задержку.

В вашем случае одна вещь, которая может быть причиной этого, является некоторой предварительной обработкой badDevices. Если, например, badDevices является QuerySet, тогда django может выполнять запрос badDevices только для подготовки SQL SQL реального запроса. Возможно, что-то подобное может происходить в случае, когда device имеет нестандартный первичный ключ.

Другая вещь может задержать подготовку SQL, конечно, django-pyodbc-azure. Возможно, он делает что-то странное при компиляции запроса, и это становится узким местом.

Это все дикие спекуляции, так что если вы все еще с этой проблемой, то размещать модели Device и Branch, а также, точное содержание badDevices и SQL генерируется из запросов. Тогда, возможно, некоторые сценарии могут быть устранены.

EDIT: Я думаю, это должно быть поле Device.uid. Возможно, django или pyodbc запутывается нестандартным основным ключом и извлекает все устройства при генерации запроса. Попробуйте две вещи:

  • Заменить device__in с device_id__in, device__pk__in и device__uid__in и проверить каждый раз. Возможно, более простой запрос будет проще для django перевести на SQL. Вы даже можете попробовать заменить branch на branch_id, на всякий случай.

  • Если выше не работает, попробуйте заменить выражение исключения с сырым SQL, где положение:

    # add quotes (because of the hyphens) & join 
    badDevicesIdString = ", ".join(["'%s'" % id for id in badDevices]) 
    
    # Replaces .exclude() 
    ... .extra(where=['device_id NOT IN (%s)' % badDevicesIdString]) 
    

Если ни работы, то, скорее всего, проблема со всем запросом и не только exclude. В этом случае есть еще несколько вариантов, но сначала попробуйте сделать это, и я буду обновлять свой ответ позже, если это необходимо.

+0

badDevices - это список python, а не набор запросов, поэтому, скорее всего, это не влияет на производительность запроса. У устройства есть первичный ключ не по умолчанию, как это повлияет на производительность запроса? Я отредактировал вопрос, чтобы добавить другие модели и сгенерированный sql. –

+0

@OzgurAkcali См. Обновленный ответ – gbs

+0

, заменяющий предложение device__in вашими предложениями, не имеет значения, но ваше второе предложение решило проблему, производительность запроса так же, как и сейчас, спасибо. Любые идеи, почему это сработало? –

Смежные вопросы