我应该以任何方式避免Django中的多表(具体)继承吗?

时间:2014-05-05 06:55:37

标签: django inheritance models multi-table-inheritance concrete-inheritance

许多有经验的开发人员建议不要使用Django multi-table inheritance,因为其效果不佳:

  1. Django gotcha: concrete inheritanceJacob Kaplan-Moss作为Django的核心撰稿人。

      

    几乎在所有情况下,抽象继承都是更好的方法   从长远来看。我看到很多网站在负载下被粉碎了   由具体的继承引入,所以我强烈建议   Django用户可以使用大型的具体继承   怀疑的剂量。

  2. {li>

    Two Scoops of Django Daniel Greenfield@pydanny

      

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

         

    不惜一切代价,每个人都应该避免多表继承   因为它增加了混乱和巨大的开销。   而不是多表继承,使用显式OneToOneFields和   模型之间的ForeignKeys,因此您可以控制何时加入   遍历。

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

    1. Reference base model in another model(必须使用GenericForeignKey或反向依赖);

    2. Get all instances of base model

      (随意添加更多内容)

    3. 那么Django中的这种继承有什么问题?为什么明确的OneToOneField更好?

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

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


      我已将具体示例移至separate question,因为此示例变得过于宽泛,并添加了使用多表继承的原因列表。

5 个答案:

答案 0 :(得分:23)

首先,继承没有对关系数据库体系结构的自然转换(好吧,我知道,Oracle Type Objects和其他一些RDBMS支持继承但django没有利用这个功能)

此时,请注意 django 为子类生成新表并编写大量left joins 以从此'子表中检索数据“的。并且left joins are not your friends。在高性能场景中,比如游戏后端或其他东西,你应该避免它并使用nulls,OneToOne或外键等一些artifaces“手动”解决继承问题。在OneToOne方案中,您可以直接或仅在需要时调用相关表。

......但......

“在我看来(TGW)” 您应该在企业项目中捕获到universe of discourse 时包含模型继承。我这样做,由于这个功能,我为我的客户节省了大量的开发时间。此外代码变得干净而优雅,这意味着易于维护(通知比这类项目没有数百或请求一秒钟)

问题提问

问:Django中这种继承有什么问题?
答:很多桌子,很多左连接。

问:为什么明确的OneToOneField更好?
答:您可以直接访问相关模型而无需左连接。

问:有没有任何说明性的例子(基准)?
答:没有可比性。

问:select_related()不允许我们控制何时调用JOIN?
答:django加入了所需的表格。

问:当我需要在另一个模型中引用基类时,多表继承有哪些替代方法?
答:无效。 OneToOne关系和许多代码行。这取决于应用需求。

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

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

<强>结论

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

答案 1 :(得分:13)

根据我的理解,您在OneToOneField RelatedModel使用BaseModel,因为最终,您希望在RelatedModel和每个Submodel1之间建立一对一的链接Submodel9BaseModel。如果是这样,那么在没有多表继承或通用关系的情况下,有一种更有效的方法。

摆脱SubmodelX并在每个OneToOneField中,RelatedModelclass 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

这将允许您使用名为RelatedModel的字段从the_thing的实例访问related_model,就像您最初提供的多表继承示例一样。

请注意,您可以使用抽象继承来分析SubModel1字段以及Submodel9ForeignKey之间的任何其他常用字段。

使用多表继承的原因是低效的,因为它为基本模型生成了一个额外的表,因此需要额外的JOIN来访问这些字段。如果您以后发现需要从RelatedModelSubmodelX的{​​{1}}字段,则使用通用关系会更有效率。但是,Django不支持select_related()中的泛型关系,您可能必须最终构建自己的查询才能有效地执行此操作。性能和编码简易性之间的权衡取决于您对服务器的预期负载以及您希望优化的时间。

答案 2 :(得分:6)

世界已经发生了变化。

首先要注意的是,在提出这个问题时,标题为Django gotcha: concrete inheritance的文章已接近四年了;从那时起,Django和RDBMs系统都取得了很大进展(例如,mysql 5.0或5.1是广泛使用的版本,5.5一般可用性还有一个月之后)。

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

确实,多表继承确实会在大多数时间后面的场景中产生额外的连接。但加入不是邪恶的。值得注意的是,在正确规范化的数据库中,您几乎总是必须加入以获取所有必需的数据。使用适当的索引时,联接不包括任何重大的性能损失。

INNER JOIN vs LEFT OUTER JOIN

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

答案 3 :(得分:1)

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)

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

这是一个简单的单元测试,其中包含一些简单的Django查询示例,显示了INNER JOINOUTER JOINSQL的相应出现次数:< / p>

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,而是故意的。

答案 4 :(得分:0)

Django按照其文档所述通过自动创建的OneToOneField实现多表继承,因此要么使用抽象继承,要么我不认为使用显式的OneToOneFields或ForeignKeys会有任何区别。