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

uta*_*ngo 40 django inheritance models concrete-inheritance multi-table-inheritance

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

  1. Django的疑难杂症:混凝土继承雅各布·卡普兰,莫斯,Django的一个核心因素.

    几乎在所有情况下,抽象继承是一种长期更好的方法.我看到在混凝土继承引入的负载下,有很多网站被粉碎,所以我强烈建议Django用户在大量怀疑的情况下使用具体的继承.

  2. Django中的两勺丹尼尔·格林菲尔德(@pydanny)

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

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

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

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

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

    (随意添加更多)

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

性能对JOIN的影响有多大?有没有显示性能差异的基准测试?

不会select_related()让我们连接被调用时控制?


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

dan*_*era 23

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

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

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

"在我看来(TGW)" 你应该在你的企业项目中包含模型继承,当它抓住你的话语世界.我这样做,由于这个功能,我为我的客户节省了大量的开发时间.另外的代码变得干净而优雅,这意味着维护方便(比通知此类项目不具有数百或请求由第二)

问题提问

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

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

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

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

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

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

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

结论

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

  • "问:为什么明确的OneToOneField更好?答:您可以直接访问相关模型而无需左连接." 这有点误导.无论如何,我认为你需要加入.不同之处在于您是明确地还是隐式地执行此操作.如果我错了,请纠正我. (5认同)
  • 在这个答案 5 年后,并阅读 Django 文档,是我还是 Django 现在自动创建了一个一对一字段?https://docs.djangoproject.com/en/1.11/topics/db/models/#multi-table-inheritance (3认同)

use*_*130 13

据我了解,你正在使用OneToOneFieldRelatedModelBaseModel,因为最终,你想之间的一到一个链接RelatedModel,并且每个Submodel1Submodel9.如果是这样,那么在没有多表继承或通用关系的情况下,有一种更有效的方法.

刚刚摆脱的BaseModel,并在每个SubmodelX,有一个OneToOneFieldRelatedModel

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()
Run Code Online (Sandbox Code Playgroud)

这将允许您SubmodelXRelatedModel使用命名字段的实例进行访问the_thing,就像您首次提供的多表继承示例一样.

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

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

  • 这仍然有效吗?我以为'related_name'必须是唯一的。 (2认同)
  • @PawelKam你是对的。您需要使用类似 `lated_name='the_thing1'` 的内容。 (2认同)

e4c*_*4c5 7

世界发生了变化.

首先要注意的是那篇名为Django的文章:在提出这个问题的时候,具体的遗产已经四年了; 从那时起,Django和RDBMs系统都取得了很大进展(例如,mysql 5.0或5.1是广泛使用的版本,5.5一般可用性仍然是一个月之后).

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

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

INNER JOIN vs LEFT OUTER JOIN

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


djv*_*jvg 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)
Run Code Online (Sandbox Code Playgroud)

默认情况下,子实例获取 aparent_ptr并且父实例可以使用childone或访问子对象(如果存在)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'))
Run Code Online (Sandbox Code Playgroud)

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

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