51

许多经验丰富的开发人员建议不要使用Django 多表继承,因为它的性能很差:

  1. Django gotcha:由Django 的核心贡献者Jacob Kaplan-Moss的具体继承。

    几乎在所有情况下,从长远来看,抽象继承都是一种更好的方法。我见过不少网站在具体继承引入的负载下崩溃,所以我强烈建议 Django 用户对具体继承的任何使用都抱有很大的怀疑态度。

  2. Daniel Greenfield ( @pydanny )的两勺Django

    多表继承,有时称为“具体继承”,被作者和许多其他开发人员认为是一件坏事。我们强烈建议不要使用它。

    不惜一切代价,每个人都应该避免多表继承,因为它会增加混乱和大量开销。代替多表继承,在模型之间使用显式 OneToOneFields 和 ForeignKeys,以便您可以控制何时遍历连接。

但是没有多表继承,我不能轻易

  1. 在另一个模型中引用基础模型(必须使用 GenericForeignKey 或反向依赖);

  2. 获取基本模型的所有实例

    (随时添加更多)

那么在 Django 中这种继承有什么问题呢?为什么显式 OneToOneFields 更好?

JOIN 对性能有多大影响?是否有任何基准可以显示性能差异?

不允许select_related()我们控制何时调用 JOIN?


我已将具体示例移至一个单独的问题,因为这个问题变得过于广泛,并添加了一系列使用多表继承的原因。

4

5 回答 5

31

首先,继承并没有自然地转换为关系数据库架构(好吧,我知道,Oracle 类型对象和其他一些 RDBMS 支持继承,但 django 不利用此功能)

此时,请注意django会为子类生成新表并写入大量left joins以从该'sub-tables'中检索数据。而左连接不是你的朋友。在高性能场景中,如游戏后端或其他东西,您应该避免它并使用一些工件(如 null、OneToOne 或外键)“手动”解决继承问题。在OneToOne场景中,您可以直接调用相关表,也可以仅在需要时调用。

... 但 ...

“在我看来(TGW)” ,当它涉及到您的讨论领域时,您应该在您的企业项目中包含模型继承。我这样做了,并且由于此功能,我为我的客户节省了大量的开发时间。此外,代码变得干净优雅,这意味着易于维护(请注意,此类项目没有数百个或每秒请求)

一问一答

问:在 Django 中这种继承有什么问题?
A:很多表,很多左连接。

问:为什么显式 OneToOneFields 更好?
A:您可以直接访问相关模型,无需左连接。

问:是否有任何说明性示例(基准)?
答:没有可比性。

问: select_related() 是否允许我们控制何时调用 JOIN?
A: django 加入需要的表。

问:当我需要在另一个模型中引用基类时,多表继承的替代方案是什么?
答:作废。OneToOne 关系和大量代码行。这取决于应用程序的需要。

问:在这种情况下,GenericForeignKeys 更好吗?
答:不适合我。

问:如果我需要 OneToOneField 作为基础模型怎么办?
答:写吧。这没有问题。例如,您可以扩展 User 模型,也可以为某些用户提供 OneToOne to User 基础模型。

结论

您应该知道在没有模型继承的情况下编写和维护代码的成本,以及支持模型继承应用程序并采取相应措施的硬件成本。

开个玩笑:你可以把它写在汇编代码上,它会运行得更快。

引用Trey Hunner 的话

您的时间通常比 CPU 的时间贵得多。

于 2014-05-08T18:09:10.643 回答
15

据我了解,您正在使用OneToOneFieldto RelatedModel,因为最终,您希望在和 每个toBaseModel之间建立一对一的链接。如果是这样,那么有一种更有效的方法可以做到这一点,而无需多表继承或泛型关系。RelatedModelSubmodel1Submodel9

只需去掉BaseModel每个中的 and SubmodelX, have a OneToOneFieldtoRelatedModel

class Submodel1(models.Model):
    related_model = models.OneToOneField(RelatedModel, null=True, blank=True, related_name='the_thing')
    some_field = models.TextField()

# ...

class Submodel9(models.Model):
    related_model = models.OneToOneField(RelatedModel, null=True, blank=True, related_name='the_thing')
    another_field = models.TextField()

这将允许您从使用名为 的字段SubmodelX的实例进行访问,就像您第一次给出的多表继承示例中一样。RelatedModelthe_thing

请注意,您可以使用抽象继承来分解related_model字段和之间SubModel1的任何其他公共字段Submodel9

使用多表继承效率低下的原因是它为基本模型生成了一个额外的表,因此需要额外的 JOIN 来访问这些字段。ForeignKey如果您后来发现您需要一个字段 fromRelatedModel到 each ,那么使用通用关系会更有效SubmodelX。但是,Django 不支持泛型关系select_related(),您可能最终必须构建自己的查询才能有效地做到这一点。性能和易于编码之间的权衡取决于您期望服务器上的负载量以及您希望花费多少时间进行优化。

于 2014-05-08T16:32:42.457 回答
14

世界变了。

首先要注意的是,标题为Django gotcha:concrete继承的文章在提出这个问题时已经将近四年了;2014 年。从那时起,Django 和 RDBMs 系统都取得了长足的进步(例如 mysql 5.0 或 5.1 是广泛使用的版本,而 5.5 的普遍可用性还有一个月的时间)。

加入我的左边,加入我的右边

确实,多表继承在大多数情况下确实会在幕后产生额外的连接。但连接并不邪恶。值得注意的是,在正确规范化的数据库中,您几乎总是必须加入以获取所有必需的数据。当使用正确的索引时,连接不会带来任何显着的性能损失。

内连接与左外连接

这确实是针对多表继承的情况,使用其他方法可以避免代价高昂的 LEFT OUTER JOIN 并改为执行 INNER JOIN 或者可能是子查询。但是对于多表继承,您将被拒绝选择

于 2016-06-16T10:19:07.323 回答
7

的发生LEFT OUTER JOIN本身是否是一个问题,我不能说,但是,无论如何,注意这些外部连接在哪些情况下实际发生可能会很有趣。

这是一个天真的尝试,使用一些示例查询来说明上述内容。

假设我们有一些使用多表继承的模型如下:

from django.db import models

class Parent(models.Model):
    parent_field = models.CharField(max_length=10)


class ChildOne(Parent):
    child_one_field = models.CharField(max_length=10)


class ChildTwo(Parent):
    child_two_field = models.CharField(max_length=10)

默认情况下,子实例获取 aparent_ptr并且父实例可以使用childoneor访问子对象(如果它们存在) childtwo。请注意,它parent_ptr表示用作主键的一对一关系(实际的子表没有id列)。

Django这是一个带有一些简单查询示例的快速而简单的单元测试,显示了INNER JOINOUTER JOIN中的相应出现次数SQL

import re
from django.test import TestCase
from inheritance.models import (Parent, ChildOne, ChildTwo)

def count_joins(query, inner_outer):
    """ Count the occurrences of JOIN in the query """
    return len(re.findall('{} join'.format(inner_outer), str(query).lower()))


class TestMultiTableInheritance(TestCase):
    def test_queries(self):
        # get children (with parent info)
        query = ChildOne.objects.all().query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # get parents
        query = Parent.objects.all().query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # filter children by parent field
        query = ChildOne.objects.filter(parent_field=parent_value).query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # filter parents by child field
        query = Parent.objects.filter(childone__child_one_field=child_value).query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))
        # get child field values via parent
        query = Parent.objects.values_list('childone__child_one_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(1, count_joins(query, 'outer'))
        # get multiple child field values via parent
        query = Parent.objects.values_list('childone__child_one_field',
                                           'childtwo__child_two_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(2, count_joins(query, 'outer'))
        # get child-two field value from child-one, through parent
        query = ChildOne.objects.values_list('parent_ptr__childtwo__child_two_field').query
        self.assertEqual(1, count_joins(query, 'inner'))
        self.assertEqual(1, count_joins(query, 'outer'))
        # get parent field value from parent, but through child
        query = Parent.objects.values_list('childone__parent_field').query
        self.assertEqual(0, count_joins(query, 'inner'))
        self.assertEqual(2, count_joins(query, 'outer'))
        # filter parents by parent field, but through child
        query = Parent.objects.filter(childone__parent_field=parent_value).query
        self.assertEqual(2, count_joins(query, 'inner'))
        self.assertEqual(0, count_joins(query, 'outer'))

请注意,并非所有这些查询都有意义:它们仅用于说明目的。

另请注意,此测试代码不是 DRY,但这是故意的。

于 2018-12-19T14:05:40.007 回答
1

正如其文档所说,Django 通过自动创建的 OneToOneField 实现多表继承。因此,要么使用抽象继承,要么我认为使用显式 OneToOneFields 或 ForeignKeys 不会产生任何差异。

于 2018-08-01T08:55:54.710 回答