【问题标题】:Is it a good practice to add extra field to database model to speed up database queries?向数据库模型添加额外字段以加快数据库查询是否是一种好习惯?
【发布时间】:2016-09-21 15:18:51
【问题描述】:

我的 Django 项目有两个数据库模型:Device 和 DeviceTest。

系统中的每个设备都应经过从制造到销售的一些测试阶段。因此很多 DeviceTest 对象通过外键关系连接到 Device 对象:

class Device(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=255)

class DeviceTest(models.Model):
    device = models.ForeignKey(Device)
    created_at = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=255)
    tester = models.CharField(max_length=255)
    action = models.CharField(max_length=255)

在我的项目中有两种页面:

1) 包含针对单个设备的所有测试的页面

2) 包含所有设备及其最新状态和操作的页面

现在我正在尝试优化 2) 页面。要获取最新的测试数据,我使用以下代码:

status_list = []
last_update_list = []
last_action_list = []

for dev in device_list:
    try:
        latest_test = DeviceTest.objects.filter(device_id=dev.pk).latest('created_at')
        status_list.append(latest_test.status)
        last_update_list.append(latest_test.created_at)
        last_action_list.append(latest_test.action)
    except ObjectDoesNotExist:
        status_list.append("Not checked")
        last_update_list.append("Not checked")
        last_action_list.append("Not checked")

目前在我的数据库中 ~600 台设备和 ~4000 次测试。这是页面加载的主要瓶颈。

有哪些方法可以加快计算速度?

我想出了在设备模型中添加额外字段的想法:在其最后一个 DeviceTest 中添加外键。在这种情况下,根本不会有任何复杂的数据库请求。

现在我有几个问题:

  • 向模型添加冗余字段是一种好习惯吗?

  • 是否可以编写迁移规则以将这个冗余字段填充到所有当前设备?

  • 最重要的是,还有哪些其他选择可以加快我的计算速度?

【问题讨论】:

    标签: django database database-design django-models django-views


    【解决方案1】:
    id_list = [dev.id for dev in device_list]
    devtests = DeviceTest.objects.filter(
        device_id__in=id_list).order_by('-created_at').distinct('device')
    

    这应该会在一次数据库调用中为您提供devtests 中每个device_id 的最新条目create_at 值。

    然后执行循环并从列表中获取值,而不是在每次迭代时调用数据库。

    但是,按照您的建议,对数据库进行非规范化也是一个好主意。使用“冗余字段”可以绝对是一个好习惯。您可以在 save() 方法中或通过侦听来自相关模型的 post_save() 信号来自动执行非规范化。

    编辑

    首先更正:应该是.distinct('device') (not created_at)

    仅从 device_list 中获取 id 值的列表解析。相当于Device.objects.filter(...).values_list('id', flat=True)

    id_list = [dev.id for dev in device_list]
    

    使用ids 列表,我们获取所有相关的DeviceTest 对象

    devtests = DeviceTest.objects.filter(device_id__in=id_list)
    

    并通过created_at 订购它们,但最新的是-created_at。这也意味着,对于每个Device,最新的相关DeviceTest 将排在第一位。

    .order_by('-created_at')
    

    最后,对于每个device,我们只选择我们找到的第一个相关值(那将是最新的,因为我们以这种方式对值进行了排序)。

    .distinct('device')
    

    此外,您还可以结合设备idDeviceTest 查找

    devtests = DeviceTest.objects.filter(device_in=Device.objects.filter(...))
    

    然后 Django 会为其创建 SQL 以在数据库中执行 JOIN,因此您无需在 Python 中加载和循环 id 列表。

    【讨论】:

    • 在 distinct 子句中使用dev.id 不是更好吗?我提出这个是因为他这样做会得到 latestDeviceTest 对象。
    • 我想应该是一样的。当id 更高时,创建时间也会更晚。假设created_at 是对象首次创建的时间。但使用id 可能会更快,尤其是在created_at 未编入索引的情况下。
    • @C14L 这给了我“此数据库后端不支持 DISTINCT ON 字段”错误。根据 Django 文档,只有 PostrgeSQL 允许使用 distinct 参数,并且我的设置中有“'ENGINE': 'django.db.backends.sqlite3'”。
    • @C14L 另外,你能向我解释一下这个查询吗?我无法理解这可能是如何工作的。在“过滤”之后,我们将获得仅与 device_list 中的 devs 相关的 DeviceTests 对象,但完全不按设备或创建时间排序?在“order_by”之后,我们将按时间排序(不是按单个设备测试时间,而是按公共时间)??之后我们将删除具有相同“creation_at”时间的重复项??看来这根本不是我们想要的,我哪里错了?
    • 你说得对,当然应该是.distinct('device'),更正了我的答案,并添加了一步一步的解释。
    猜你喜欢
    • 1970-01-01
    • 2021-12-18
    • 2020-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多