Djangoモデルの主キーとしてUUIDを使用する(一般的な関係への影響)


90

いくつかの理由で^、いくつかのDjangoモデルで主キーとしてUUIDを使用したいと思います。そうする場合でも、ContentTypeを介してジェネリックリレーションを使用する「contrib.comments」、「django-voting」、「django-tagging」などの外部アプリを使用できますか?

例として「django-voting」を使用すると、投票モデルは次のようになります。

class Vote(models.Model):
    user         = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id    = models.PositiveIntegerField()
    object       = generic.GenericForeignKey('content_type', 'object_id')
    vote         = models.SmallIntegerField(choices=SCORES)

このアプリは、投票されるモデルの主キーが整数であると想定しているようです。

ただし、組み込みのコメントアプリは、整数以外のPKを処理できるようです。

class BaseCommentAbstractModel(models.Model):
    content_type   = models.ForeignKey(ContentType,
            verbose_name=_('content type'),
            related_name="content_type_set_for_%(class)s")
    object_pk      = models.TextField(_('object ID'))
    content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")

この「integer-PK-assumed」問題は、UUIDの使用を困難にするサードパーティアプリの一般的な状況ですか?または、おそらく、私はこの状況を誤解していますか?

あまり問題を起こさずにDjangoの主キーとしてUUIDを使用する方法はありますか?


^いくつかの理由:オブジェクト数の非表示、URLの「IDクロール」の防止、複数のサーバーを使用した競合しないオブジェクトの作成、...

回答:


55

UUID主キーは、一般的な関係だけでなく、一般的な効率にも問題を引き起こします。すべての外部キーは、マシンワードよりも保存と結合の両方で大幅にコストがかかります。

ただし、UUIDを主キーにする必要はありません。モデルに。を使用してuuidフィールドを追加することにより、UUIDをキーにするだけunique=Trueです。通常どおり(システムの内部)暗黙の主キーを使用し、外部識別子としてUUIDを使用します。


16
Joe Holloway、その必要はありませんdefault。フィールドのとしてUUID生成関数を指定するだけです。
Pi Delport 2010年

4
Joe:django_extensions.db.fields.UUIDFieldを使用して、モデルにUUIDを作成します。簡単です。フィールドを次のように定義します。user_uuid= UUIDField()
mitchf 2010年

3
@MatthewSchinckel:mitchfがdjango_extensions.db.fields.UUIDField述べたように使用すると、Django-Southの移行に問題はありません-彼が言及したフィールドには、Southの移行のサポートが組み込まれています。
タデック2012

126
ひどい答え。Postgresにはネイティブ(128ビット)UUIDがあり、64ビットマシンでは2ワードしかないため、ネイティブ64ビットINTよりも「大幅に高価」になることはありません。
ポストフューチュリスト2013

8
ピエト、btreeインデックスがあるとすると、特定のクエリでいくつの比較が行われるのでしょうか。多くはありません。また、memcmp呼び出しは、ほとんどのOSで調整および最適化されると確信しています。質問の性質に基づいて、パフォーマンスの違いが(無視できる可能性が高い)可能性があるためにUUIDを使用しないことは、最適化が間違っていると言えます。
ポストフューチュリスト2013年

219

ドキュメント見られるように、 Django1.8からは組み込みのUUIDフィールドがあります。UUIDと整数を使用した場合のパフォーマンスの違いはごくわずかです。

import uuid
from django.db import models

class MyUUIDModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

詳細については、この回答確認することもできます。


@KeithhackbarthテーブルのIDを自動的に作成するときに毎回これを使用するようにdjangoを設定するにはどうすればよいですか?
anon58192932 2017年

3
@ anon58192932「毎回」とはどういう意味かはっきりしていません。すべてのモデルでUUIDを使用する場合は、独自の抽象ベースモデルを作成し、django.models.Modelの代わりに使用します。
НазарТопольський

4
パフォーマンスの違いは、基盤となるデータベースがUUIDタイプをサポートしている場合にのみ無視できます。DjangoはまだほとんどのDBにcharfieldを使用しています(postgresqlはUUIDフィールドをサポートする唯一の文書化されたdbです)。
NirIzr

なぜこれが人気のある答えなのか混乱しています...質問はサードパーティのパッケージの難しさについて尋ねていました。DjangoはネイティブにUUIDをサポートしていますが、UUIDを考慮しないパッケージがまだいくつかあるようです。私の経験では、それは苦痛です。
ambe5960

12

私は似たような状況に遭遇したとして見つけた公式のDjangoドキュメントことを、object_id同じタイプである必要はありませんPRIMARY_KEY関連モデルの。あなたの一般的な関係は、両方のために有効にする場合たとえば、IntegerFieldとし、CharFieldですIDの、ちょうどあなたを設定object_idするCharFieldです。整数は文字列に強制変換できるため、問題ありません。UUIDFieldについても同じことが言えます。

例:

class Vote(models.Model):
    user         = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id    = models.CharField(max_length=50) # <<-- This line was modified 
    object       = generic.GenericForeignKey('content_type', 'object_id')
    vote         = models.SmallIntegerField(choices=SCORES)

4

PKとしてのUUIDの実際の問題は、数値以外の識別子に関連するディスクの断片化と挿入の劣化です。PKはクラスター化インデックスであるため、自動インクリメントされない場合、DBエンジンは、通常より低いIDの行を挿入するときに、物理ドライブを再利用する必要があります。これは、UUIDで常に発生します。DBに大量のデータを取得する場合、1つの新しいレコードを挿入するだけで数秒または数分かかる場合があります。そして、ディスクは最終的に断片化され、定期的なディスクの最適化が必要になります。これはすべて本当に悪いです。

これらを解決するために、私は最近、共有する価値があると思った次のアーキテクチャを思いつきました。

UUID疑似プライマリキー

この方法では、UUIDの利点を主キーとして(一意のインデックスUUIDを使用して)活用しながら、自動インクリメントされたPKを維持して、非数値PKを持つことによる断片化と挿入パフォーマンスの低下の懸念に対処できます。

使い方:

  1. pkidDBモデルで呼び出される自動インクリメントされた主キーを作成します。
  2. 一意のインデックスが付けられたUUIDidフィールドを追加して、数値の主キーの代わりにUUIDIDで検索できるようにします。
  3. to_field='id'外部キーを(を使用して)UUIDにポイントし、外部キーが数値IDではなく疑似PKを適切に表すことができるようにします。

基本的に、次のことを行います。

まず、抽象的なDjangoベースモデルを作成します

class UUIDModel(models.Model):
    pkid = models.BigAutoField(primary_key=True, editable=False)
    id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)

    class Meta:
        abstract = True

モデルではなく、必ずベースモデルを拡張してください。モデル

class Site(UUIDModel):
    name = models.CharField(max_length=255)

また、ForeignKeysidが自動インクリメントpkidフィールドではなくUUIDフィールドを指していることを確認してください。

class Page(UUIDModel):
    site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)

Django Rest Framework(DRF)を使用している場合は、Base ViewSetクラスも作成して、デフォルトの検索フィールドを設定してください。

class UUIDModelViewSet(viewsets.ModelViewSet):
    lookup_field = 'id' 

そして、APIビューのベースModelViewSetの代わりにそれを拡張します。

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

この記事の理由と方法に関するその他の注意事項:https//www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps


0

これは、次の手順を使用して、カスタムベース抽象モデルを使用して実行できます。

最初にプロジェクトにbasemodelというフォルダーを作成し、次に次のようにabstractmodelbase.pyを追加します。

from django.db import models
import uuid


class BaseAbstractModel(models.Model):

    """
     This model defines base models that implements common fields like:
     created_at
     updated_at
     is_deleted
    """
    id=models.UUIDField(primary_key=True, ,unique=True,default=uuid.uuid4, editable=False)
    created_at=models.DateTimeField(auto_now_add=True,editable=False)
    updated_at=models.DateTimeField(auto_now=True,editable=False)
    is_deleted=models.BooleanField(default=False)

    def soft_delete(self):
        """soft  delete a model instance"""
        self.is_deleted=True
        self.save()

    class Meta:
        abstract=True
        ordering=['-created_at']

2番目:各アプリのすべてのモデルファイルでこれを行います

from django.db import models
from basemodel import BaseAbstractModel
import uuid

# Create your models here.

class Incident(BaseAbstractModel):

    """ Incident model  """

    place = models.CharField(max_length=50,blank=False, null=False)
    personal_number = models.CharField(max_length=12,blank=False, null=False)
    description = models.TextField(max_length=500,blank=False, null=False)
    action = models.TextField(max_length=500,blank=True, null=True)
    image = models.ImageField(upload_to='images/',blank=True, null=True)
    incident_date=models.DateTimeField(blank=False, null=False) 

したがって、上記のモデルインシデントは、baseabstractモデルのすべてのフィールドに固有のものです。


-1

この質問は、「Djangoに、自動インクリメントされた整数の代わりに、すべてのテーブルのすべてのデータベースIDにUUIDを使用させる方法はありますか?」と言い換えることができます。

確かに、私はできる:

id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

私のすべてのテーブルで、しかし私はこれを行う方法を見つけることができません:

  1. サードパーティモジュール
  2. DjangoはManyToManyテーブルを生成しました

したがって、これはDjangoの機能が不足しているようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.