django排除性能问题

时间:2015-10-14 11:25:00

标签: sql-server django django-pyodbc

我有一个Django 1.8应用程序,我使用的是MsSQL数据库,pyodbc作为db后端(使用“django-pyodbc-azure”模块)。

我有以下型号:

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是一个预填充的设备ID列表,包含大约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)))))

因此,使用数据库级别的排除似乎不会影响查询性能,但在django中,如果添加排除部分,查询运行速度会慢6倍。可能导致这种情况的原因是什么?

2 个答案:

答案 0 :(得分:3)

一般问题似乎是django正在做一些额外的工作来准备exclude条款。在该步骤之后,当SQL生成并发送到数据库时,django方面没有任何有趣的事情可能导致如此显着的延迟。

在您的情况下,可能导致这种情况的一件事是badDevices的某种预处理。例如,如果badDevicesQuerySet,那么django可能正在执行badDevices查询,只是为了准备实际查询的SQL。在device具有非默认主键的情况下,可能会发生类似情况。

另一件事可能会延迟SQL准备当然是django-pyodbc-azure。也许它在编译查询时做了一些奇怪的事情,它就成了一个瓶颈。

这是所有疯狂的推测,所以如果你仍然遇到这个问题,那么也发布DeviceBranch模型,badDevices的确切内容和从查询生成的SQL。那么也许至少可以消除一些场景。

编辑:我认为它必须是Device.uid字段。可能django或pyodbc被非默认主键混淆,并在生成查询时获取所有设备。尝试两件事:

  • device__in替换为device_id__indevice__pk__indevice__uid__in并再次检查每个branch。也许更明确的查询将更容易让django转换为SQL。您甚至可以尝试将branch_id替换为# 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]) ,以防万一。

  • 如果上述方法无效,请尝试使用原始SQL where子句替换exclude表达式:

    exclude

如果两者都不起作用,那么很可能问题在于整个查询而不只是secondValue。在这种情况下还有一些选项,但首先尝试以上内容,如有必要,我会在稍后更新我的答案。

答案 1 :(得分:1)

只想分享我在MySQL中遇到的类似问题,并排除子句的性能及其修复方法。

运行exclude子句时,带有“ in”查找的列表实际上是我使用values_list方法获得的查询集。检查MySQL执行的排除查询,“ in”对象不是值,实际上是另一个查询。此行为会影响特定大型查询的性能。

为了解决这个问题,我没有传递查询集,而是将其放在python值列表中。这样,每个值都将作为参数传递给in查找中,并且性能得到了真正的改善。