我有一个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倍。可能导致这种情况的原因是什么?
答案 0 :(得分:3)
一般问题似乎是django正在做一些额外的工作来准备exclude
条款。在该步骤之后,当SQL生成并发送到数据库时,django方面没有任何有趣的事情可能导致如此显着的延迟。
在您的情况下,可能导致这种情况的一件事是badDevices
的某种预处理。例如,如果badDevices
是QuerySet
,那么django可能正在执行badDevices
查询,只是为了准备实际查询的SQL。在device
具有非默认主键的情况下,可能会发生类似情况。
另一件事可能会延迟SQL准备当然是django-pyodbc-azure
。也许它在编译查询时做了一些奇怪的事情,它就成了一个瓶颈。
这是所有疯狂的推测,所以如果你仍然遇到这个问题,那么也发布Device
和Branch
模型,badDevices
的确切内容和从查询生成的SQL。那么也许至少可以消除一些场景。
编辑:我认为它必须是Device.uid
字段。可能django或pyodbc被非默认主键混淆,并在生成查询时获取所有设备。尝试两件事:
将device__in
替换为device_id__in
,device__pk__in
和device__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查找中,并且性能得到了真正的改善。