Стоит ли каким-либо образом избегать многотабличного (конкретного) наследования в Django?

Многие опытные разработчики не рекомендуют использовать наследование нескольких таблиц Django из-за его низкой производительности:

  1. # P2 #
    # P3 #
  2. # P4 #
    # P5 # # P6 #

Но без многотабличного наследования я не могу легко

  1. Эталонная базовая модель в другой модели (необходимо использовать GenericForeignKey или обратную зависимость);

  2. Получите все экземпляры базовой модели.

    (можете добавить больше)

Так что же плохого в таком наследовании в Django? Почему явные OneToOneFields лучше?

Насколько сильно страдает производительность от JOINs? Есть ли какие-нибудь тесты, показывающие разницу в производительности?

Разве select_related() не позволяет нам контролировать, когда вызываются соединения JOIN?


Я переместил конкретные примеры в отдельный вопрос, поскольку этот вопрос становится слишком общим, и добавлен список причин для использования наследования нескольких таблиц.


person utapyngo    schedule 05.05.2014    source источник
comment
What are the alternatives to multi-table inheritance when I need to reference a base class in another model? Не могли бы вы пояснить, что вы имеете в виду?   -  person ptr    schedule 08.05.2014
comment
Несколько простых примеров прояснят вопрос и позволят получить более точные ответы. Спасибо.   -  person alecxe    schedule 08.05.2014
comment
@alecxe: Я добавил несколько примеров, объясняющих, что я имею в виду, ссылаясь на этот базовый класс в другой модели.   -  person utapyngo    schedule 08.05.2014
comment
Слишком много вопросов в одном. Я переместил часть этого вопроса с примерами кода в отдельный: stackoverflow.com/questions/23555478/   -  person utapyngo    schedule 09.05.2014


Ответы (5)


Прежде всего, наследование не имеет естественного преобразования в архитектуру реляционной базы данных (хорошо, я знаю, объекты Oracle Type и некоторые другие СУБД поддерживают наследование, но django не использует эту функциональность)

На этом этапе обратите внимание, что django генерирует новые таблицы для подклассов и записывает много left joins для извлечения данных из этих "подтаблиц". И левые присоединения не являются вашими друзьями. В сценариях с высокой производительностью, таких как серверная часть игры или что-то еще, вы должны избегать этого и разрешать наследование «вручную» с некоторыми артефактами, такими как нули, OneToOne или внешние ключи. В OneToOne сценарии вы можете вызвать связанную таблицу напрямую или только при необходимости.

... НО ...

На мой взгляд (TGW) вам следует включить наследование модели в свои корпоративные проекты, когда оно попадает в ваш дискурсивная вселенная. Я делаю это и благодаря этой функции экономлю много времени для своих клиентов. Кроме того, код становится чистым и элегантным, что означает простоту обслуживания (обратите внимание, что у таких проектов нет сотен запросов или запросов в секунду).

Вопрос за вопросом

В: Что не так с таким наследованием в Django?
A: Много таблиц, много левых соединений.

Вопрос: Почему явные OneToOneFields лучше?
A: Вы можете получить прямой доступ к связанной модели без левых соединений.

Q: Есть ли наглядные примеры (тесты)?
A: Нет сопоставимых.

В: Разве select_related () не позволяет нам контролировать, когда вызываются JOIN?
A: django объединяет необходимые таблицы.

В: Каковы альтернативы многотабличному наследованию, когда мне нужно ссылаться на базовый класс в другой модели?
A: Аннулирование. Отношения OneToOne и множество строк кода. Это зависит от потребностей приложения.

Q: GenericForeignKeys лучше в этом случае?
A: Не для меня.

Вопрос: Что делать, если мне требуется OneToOneField для базовой модели?
A: Напишите это. С этим проблем нет. Например, вы можете расширить модель User, а также у вас может быть базовая модель OneToOne to User для некоторых пользователей.

Заключение

Вы должны знать стоимость написания и обслуживания кода без наследования модели, а также стоимость оборудования для поддержки приложений с наследованием модели и действовать соответствующим образом.

Шутка: можно написать на ассемблерном коде, и он будет работать быстрее.

person dani herrera    schedule 08.05.2014
comment
Вопрос: Почему явные OneToOneFields лучше? О: Вы можете получить прямой доступ к связанной модели без левых соединений. это несколько вводит в заблуждение. Я думаю, вам все равно нужно присоединиться. Разница в том, делаете ли вы это явно или неявно. Пожалуйста, поправьте меня, если я ошибаюсь. - person eugene; 04.03.2015
comment
@eugene, ты прав. Будьте свободны улучшать ответ. Спасибо! - person dani herrera; 04.03.2015
comment
Спустя 5 лет после этого ответа и чтения Django Docs, я или Django теперь автоматически создали однозначное поле? docs.djangoproject.com/en/1.11/topics / db / models / - person Anthony; 02.10.2019

Насколько я понимаю, вы используете OneToOneField на RelatedModel BaseModel, потому что в конечном итоге вам нужна прямая связь между RelatedModel и каждым Submodel1 на Submodel9. Если это так, существует более эффективный способ сделать это без наследования нескольких таблиц и общих отношений.

Просто избавьтесь от BaseModel и в каждом SubmodelX поставьте от OneToOneField до RelatedModel

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 из экземпляра RelatedModel, используя поле с именем the_thing, как в первом примере наследования нескольких таблиц.

Обратите внимание, что вы можете использовать абстрактное наследование, чтобы исключить поле related_model и любые другие общие поля от SubModel1 до Submodel9.

Причина, по которой использование наследования нескольких таблиц неэффективно, заключается в том, что оно создает дополнительную таблицу для базовой модели и, следовательно, дополнительные JOINs для доступа к этим полям. Использование общих отношений будет более эффективным, если позже вы обнаружите, что вместо этого вам нужно поле ForeignKey от RelatedModel до каждого SubmodelX. Однако Django не поддерживает общие отношения в select_related(), и вам, возможно, придется создавать собственные запросы, чтобы сделать это эффективно. Выбор между производительностью и простотой кодирования зависит от вас, в зависимости от того, какую нагрузку вы ожидаете на сервере и сколько времени вы хотите потратить на оптимизацию.

person user193130    schedule 08.05.2014
comment
Я переместил часть своего вопроса с примерами кода и часть вашего ответа в отдельный вопрос. Вы не возражаете? Если вы разместите там ответ, я удалю свой ответ, который ссылается на этот. stackoverflow.com/a/23555565/517316 - person utapyngo; 09.05.2014
comment
Это все еще работает? Я подумал, что related_name должно быть уникальным. - person Pawel Kam; 11.04.2019
comment
@PawelKam, вы правы. Вместо этого вам нужно будет использовать что-то вроде related_name='the_thing1'. - person Lord Elrond; 03.10.2019
comment
Чем ваше решение более эффективно, чем наследование нескольких таблиц? См. мой ответ на связанный question за более подробную критику этого. - person Lord Elrond; 03.10.2019

Мир изменился.

Прежде всего следует отметить, что статье под названием Django gotcha: конкретное наследование было почти четыре года. в то время, когда был задан этот вопрос; в 2014 году. С тех пор как Django, так и RDBM-системы прошли долгий путь (например, mysql 5.0 или 5.1 были широко используемыми версиями, а до общего выпуска 5.5 оставался еще один месяц).

Присоединяется слева от меня, присоединяется справа от меня

Это правда, что наследование нескольких таблиц действительно приводит к дополнительным объединениям за кулисами большую часть времени. Но присоединения - это не зло. Стоит отметить, что в правильно нормализованной базе данных вам почти всегда нужно присоединиться, чтобы получить все необходимые данные. Когда используются правильные индексы, объединения не включают каких-либо значительных потерь производительности.

ВНУТРЕННЕЕ СОЕДИНЕНИЕ против ЛЕВОГО ВНЕШНЕГО СОЕДИНЕНИЯ

Это действительно относится к наследованию нескольких таблиц, с помощью других подходов можно избежать дорогостоящего LEFT OUTER JOIN и вместо этого выполнить INNER JOIN или, возможно, подзапрос. Но с наследованием нескольких таблиц вам отказывают в таком выборе

person e4c5    schedule 16.06.2016

Я не могу сказать, является ли появление 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, а родительские экземпляры могут обращаться к дочерним объектам (если они существуют) с помощью childone или childtwo. Обратите внимание, что parent_ptr представляет собой взаимно-однозначное отношение, которое используется в качестве первичного ключа (фактические дочерние таблицы не имеют столбца id).

Вот быстрый и грязный модульный тест с некоторыми наивными примерами Django запросов, показывающий соответствующее количество вхождений INNER JOIN и OUTER 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'))

Обратите внимание, что не все эти запросы имеют смысл: они предназначены только для иллюстративных целей.

Также обратите внимание, что этот тестовый код не является СУХИМ, но это сделано специально.

person djvg    schedule 19.12.2018

Django реализует многотабличное наследование через автоматически созданный OneToOneField, как говорится в его документации. Поэтому либо используйте абстрактное наследование, либо я не думаю, что использование явного OneToOneFields или ForeignKeys имеет какие-либо различия.

person youngjack    schedule 01.08.2018