首页 文章

django排除性能问题

提问于
浏览
1

我有一个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倍 . 可能是什么导致了这个?

1 回答

  • 2

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

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

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

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

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

    • device_id__indevice__pk__indevice__uid__in 替换 device__in 并再次检查每一个 . 也许更明确的查询将更容易将django转换为SQL . 你甚至可以尝试用 branch_id 替换 branch ,以防万一 .

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

    # 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 . 在这种情况下还有一些选项,但首先尝试以上内容,如有必要,我会在稍后更新我的答案 .

相关问题