django 排除的性能问题
Performance issue with django exclude
我有一个 Django 1.8 应用程序,我正在使用 MsSQL 数据库,pyodbc 作为数据库后端(使用 "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 倍。可能是什么原因造成的?
一般的问题似乎是 django 正在做一些额外的工作来准备 exclude
子句。在那一步之后,到 SQL 已生成并发送到数据库时,django 端没有发生任何有趣的事情可能导致如此严重的延迟。
在您的情况下,可能导致这种情况的一件事是 badDevices
的某种预处理。例如,如果 badDevices
是 QuerySet
,那么 django 可能正在执行 badDevices
查询只是为了准备实际查询的 SQL。在 device
具有非默认主键的情况下可能会发生类似的事情。
另一件事可能会延迟 SQL 准备工作,当然是 django-pyodbc-azure
。也许它在编译查询时做了一些奇怪的事情,它成为了一个瓶颈。
虽然这都是疯狂的猜测,所以如果您仍然遇到这个问题,那么 post Device
和 Branch
模型,[=12 的确切内容=] 和从查询生成的 SQL。那么也许至少可以消除一些场景。
编辑:我认为它一定是 Device.uid
字段。可能 django 或 pyodbc 对非默认主键感到困惑,并在生成查询时获取所有设备。尝试两件事:
将device__in
替换为device_id__in
、device__pk__in
和device__uid__in
并再次检查每一项。也许更明确的查询会更容易让 django 翻译成 SQL。您甚至可以尝试将 branch
替换为 branch_id
,以防万一。
如果上述方法不起作用,请尝试将排除表达式替换为原始 SQL where 子句:
# 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
。在这种情况下还有更多选择,但请先尝试上面的方法,如有必要,我稍后会更新我的答案。
只想分享我在 MySQL 和排除子句性能方面遇到的类似问题,以及它是如何修复的。
当运行 exclude 子句时,带有"in" 查找的列表实际上是我使用values_list 方法得到的Queryset。检查 MySQL 执行的排除查询,"in" 对象不是值,实际上是另一个查询。此行为影响特定大型查询的性能。
为了解决这个问题,我没有传递查询集,而是将其平放在一个 python 值列表中。通过这样做,每个值都作为参数传递到 in 查找中,性能得到了真正的提高。
我有一个 Django 1.8 应用程序,我正在使用 MsSQL 数据库,pyodbc 作为数据库后端(使用 "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 倍。可能是什么原因造成的?
一般的问题似乎是 django 正在做一些额外的工作来准备 exclude
子句。在那一步之后,到 SQL 已生成并发送到数据库时,django 端没有发生任何有趣的事情可能导致如此严重的延迟。
在您的情况下,可能导致这种情况的一件事是 badDevices
的某种预处理。例如,如果 badDevices
是 QuerySet
,那么 django 可能正在执行 badDevices
查询只是为了准备实际查询的 SQL。在 device
具有非默认主键的情况下可能会发生类似的事情。
另一件事可能会延迟 SQL 准备工作,当然是 django-pyodbc-azure
。也许它在编译查询时做了一些奇怪的事情,它成为了一个瓶颈。
虽然这都是疯狂的猜测,所以如果您仍然遇到这个问题,那么 post Device
和 Branch
模型,[=12 的确切内容=] 和从查询生成的 SQL。那么也许至少可以消除一些场景。
编辑:我认为它一定是 Device.uid
字段。可能 django 或 pyodbc 对非默认主键感到困惑,并在生成查询时获取所有设备。尝试两件事:
将
device__in
替换为device_id__in
、device__pk__in
和device__uid__in
并再次检查每一项。也许更明确的查询会更容易让 django 翻译成 SQL。您甚至可以尝试将branch
替换为branch_id
,以防万一。如果上述方法不起作用,请尝试将排除表达式替换为原始 SQL where 子句:
# 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
。在这种情况下还有更多选择,但请先尝试上面的方法,如有必要,我稍后会更新我的答案。
只想分享我在 MySQL 和排除子句性能方面遇到的类似问题,以及它是如何修复的。
当运行 exclude 子句时,带有"in" 查找的列表实际上是我使用values_list 方法得到的Queryset。检查 MySQL 执行的排除查询,"in" 对象不是值,实际上是另一个查询。此行为影响特定大型查询的性能。
为了解决这个问题,我没有传递查询集,而是将其平放在一个 python 值列表中。通过这样做,每个值都作为参数传递到 in 查找中,性能得到了真正的提高。