web-dev-qa-db-ja.com

Django)でマルチテーブル(コンクリート)の継承を回避する必要はありますか?

パフォーマンスが低いため、多くの経験豊富な開発者は Djangoマルチテーブル継承 の使用を推奨していません。

  1. Django gotcha:具体的な継承Jacob Kaplan-Moss による、Djangoのコアコントリビューター。

    ほとんどすべての場合において、抽象継承は長期的にはより優れたアプローチです。具体的な継承によってもたらされる負荷でサイトがつぶれることはほとんどないので、Djangoユーザーは、大量の疑念を抱いて具体的な継承の使用にアプローチすることを強くお勧めします。

  2. Djangoの2つのスクープDaniel Greenfield によって( @ pydanny

    「具体的な継承」と呼ばれることもあるマルチテーブル継承は、作成者や他の多くの開発者によって悪いことだと考えられています。使用しないことを強くお勧めします。

    混乱とかなりのオーバーヘッドの両方を追加するため、すべてのコストで、誰もがマルチテーブル継承を回避する必要があります。複数テーブルの継承の代わりに、モデル間で明示的なOneToOneFieldsおよびForeignKeysを使用して、結合がいつトラバースされるかを制御できるようにします。

しかし、マルチテーブル継承がなければ、私は簡単にはできません

  1. 別のモデルの参照ベースモデル (GenericForeignKeyを使用するか、依存関係を逆にする必要があります);

  2. 基本モデルのすべてのインスタンスを取得

    (自由に追加してください)

では、このようなDjangoの継承の何が問題になっていますか?明示的なOneToOneFieldsが優れているのはなぜですか?

JOINによってパフォーマンスはどの程度低下しますか?パフォーマンスの違いを示すベンチマークはありますか?

select_related()では、JOINが呼び出されるタイミングを制御できませんか?


具体例を 別の質問 に移動しました。これは広範になりすぎているためです。代わりに、マルチテーブル継承を使用する理由のリストを追加しました。

42
utapyngo

まず、継承はリレーショナルデータベースアーキテクチャへの自然な変換ではありません(わかりました、Oracle Type Objectsと他のいくつかのRDBMSは継承をサポートしていますが、Djangoはこの機能を利用していません)

この時点で、Djangoがサブクラスに新しいテーブルを生成し、[lots of left joins]を書き込んでデータを取得することに注意してください 'サブテーブル'。そして 左の結合はあなたの友達ではありません 。ゲームバックエンドなどの高パフォーマンスシナリオでは、それを回避し、null、OneToOne、外部キーなどのアーティフェイスを使用して継承を「手動」で解決する必要があります。 OneToOneシナリオでは、関連テーブルを直接呼び出すか、必要な場合にのみ呼び出すことができます。

...しかし...

"私の意見では(TGW)"エンタープライズプロジェクトにモデルの継承が含まれる場合は、継承する必要があります 談話の世界。私はこれを行い、この機能のおかげで顧客の開発時間を大幅に節約できます。また、コードはクリーンでエレガントになりますで、メンテナンスが容易になります(この種のプロジェクトには数百のリクエストや1秒ごとのリクエストがないことに注意してください)

質問による質問

Q:Djangoでのこの種の継承の何が問題になっていますか?
A:たくさんのテーブル、たくさんの左結合。

Q:明示的なOneToOneFieldsの方が優れているのはなぜですか?
A:左結合なしで関連モデルに直接アクセスできます。

Q:例示的な例(ベンチマーク)はありますか?
A:比較できません。

Q:select_related()では、JOINが呼び出されるタイミングを制御できませんか?
A:Djangoは必要なテーブルを結合します。

Q:別のモデルの基本クラスを参照する必要がある場合、マルチテーブル継承の代替手段は何ですか?
A:無効化。 OneToOne関係と多数のコード行。アプリケーションのニーズによって異なります。

Q:この場合、GenericForeignKeysの方が優れていますか?
A:いいえ。

Q:ベースモデルにOneToOneFieldが必要な場合はどうなりますか? A:それを書いてください。これで問題ありません。たとえば、ユーザーモデルを拡張したり、一部のユーザーのOneToOne to User基本モデルを作成したりできます。

結論

モデルを継承しないコードの作成と保守のコスト、およびモデルの継承アプリケーションをサポートし、それに応じて動作するためのハードウェアのコストも知っておく必要があります。

23
dani herrera

私が理解していることから、OneToOneFieldからRelatedModelまでBaseModelを使用しているため、最終的にはRelatedModelと_Submodel1_から_Submodel9_まで。もしそうなら、マルチテーブル継承やジェネリックリレーションなしでそれを行うより効率的な方法があります。

BaseModelを削除し、各SubmodelXOneToOneFieldRelatedModel

_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()
_

これにより、最初に指定した複数テーブルの継承の例と同じように、_the_thing_というフィールドを使用して、SubmodelXのインスタンスからRelatedModelにアクセスできます。

抽象継承を使用して、_related_model_フィールドと、_SubModel1_から_Submodel9_の間の他の一般的なフィールドを除外することができます。

マルチテーブル継承を使用するのが非効率的である理由は、ベースモデル用の追加のテーブルが生成されるため、これらのフィールドにアクセスするための追加のJOINが生成されるためです。後で[ForeignKeyから各RelatedModelへのSubmodelXフィールドが必要になると後でわかった場合は、ジェネリックリレーションを使用する方が効率的です。ただし、Django=はselect_related()の一般的な関係をサポートしていないため、効率的に行うには独自のクエリを作成する必要がある場合があります。パフォーマンスとコーディングの容易さのトレードオフはサーバーで予想される負荷の量と、最適化に費やす時間の長さによって異なります。

13
user193130

世界は変わりました。

最初に注意する点は、 Djangoの落とし穴:具体的な継承 というタイトルの記事は、この質問が行われた時点で4歳近くだったことです。 DjangoとRDBMsシステムの両方がそれ以来長い道のりを歩んできました(例mysql 5.0または5.1は広く使用されているバージョンであり、5.5の一般出荷はまだ1か月先でした)。

私の左に参加し、私の右に参加

複数テーブルの継承により、ほとんどの場合、バックグラウンドで追加の結合が発生することは事実です。しかし、結合は悪ではありません。適切に正規化されたデータベースでは、ほとんどすべての場合、結合して必要なデータをすべてフェッチする必要があることに注意してください。適切なインデックスが使用されている場合、結合には大きなパフォーマンス上のペナルティは含まれません。

内部結合と左外部結合

これは確かに、複数テーブルの継承に反するケースです。他のアプローチでは、コストのかかるLEFT OUTER JOINを回避して、代わりに、またはおそらくサブクエリでINNER JOINを実行することが可能です。しかし、マルチテーブル継承では、その選択が拒否されます

8
e4c5

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は、主キーとして使用される1対1の関係を表すことに注意してください(実際の子テーブルにはid列がありません)。

以下は、素朴なDjangoクエリの例を使用した簡単な単体テストです。SQL内のINNER JOINおよびOUTER JOINの対応する発生回数を示しています。

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
djvg

Djangoは、ドキュメントで述べられているように、自動作成されたOneToOneFieldを介して複数テーブルの継承を実装しています。

0
youngjack