Menggunakan UUID sebagai kunci utama dalam model Django (dampak hubungan umum)

91

Untuk sejumlah alasan ^, saya ingin menggunakan UUID sebagai kunci utama dalam beberapa model Django saya. Jika saya melakukannya, apakah saya masih dapat menggunakan aplikasi luar seperti "contrib.comments", "django-voting" atau "django-tagging" yang menggunakan hubungan umum melalui ContentType?

Menggunakan "django-voting" sebagai contoh, model Vote terlihat seperti ini:

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)

Aplikasi ini tampaknya mengasumsikan bahwa kunci utama untuk model yang dipilih adalah bilangan bulat.

Aplikasi komentar bawaan tampaknya mampu menangani PK non-integer, meskipun:

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")

Apakah masalah "diasumsikan PK dengan bilangan bulat" ini merupakan situasi umum untuk aplikasi pihak ketiga yang akan membuat penggunaan UUID menyebalkan? Atau, mungkin, apakah saya salah membaca situasi ini?

Adakah cara untuk menggunakan UUID sebagai kunci utama di Django tanpa menyebabkan terlalu banyak masalah?


^ Beberapa alasan: menyembunyikan jumlah objek, mencegah url "perayapan id", menggunakan beberapa server untuk membuat objek yang tidak bertentangan, ...

mitchf
sumber

Jawaban:

56

Kunci utama UUID akan menyebabkan masalah tidak hanya dengan relasi generik, tetapi juga dengan efisiensi secara umum: setiap kunci asing akan jauh lebih mahal — baik untuk disimpan maupun digabungkan — daripada kata mesin.

Namun, tidak ada yang mengharuskan UUID menjadi kunci utama: cukup jadikan sebagai kunci sekunder , dengan melengkapi model Anda dengan bidang uuid dengan unique=True. Gunakan kunci primer implisit seperti biasa (internal ke sistem Anda), dan gunakan UUID sebagai pengenal eksternal Anda.

Pi Delport
sumber
16
Joe Holloway, tidak perlu itu: Anda cukup menyediakan fungsi pembuatan UUID sebagai bidang default.
Pi Delport
4
Joe: Saya menggunakan django_extensions.db.fields.UUIDField untuk membuat UUID saya di model saya. Sederhana, saya hanya mendefinisikan bidang saya seperti ini: user_uuid = UUIDField ()
mitchf
3
@MatthewSchinckel: Ketika Anda menggunakan django_extensions.db.fields.UUIDFieldseperti yang disebutkan oleh mitchf, Anda tidak akan mendapatkan masalah dengan migrasi Django-Selatan - bidang yang disebutkan olehnya memiliki dukungan bawaan untuk migrasi Selatan.
Tadeck
126
Jawaban yang mengerikan. Postgres memiliki UUID asli (128 bit) yang hanya terdiri dari 2 kata pada mesin 64 bit, jadi tidak akan "jauh lebih mahal" daripada INT 64 bit asli.
postfuturist
8
Piet, mengingat ada indeks btree di atasnya, berapa banyak perbandingan yang akan ada pada kueri tertentu? Tidak banyak. Selain itu, saya yakin bahwa panggilan memcmp akan disejajarkan dan dioptimalkan di sebagian besar OS. Berdasarkan sifat pertanyaannya, saya akan mengatakan tidak menggunakan UUID karena kemungkinan perbedaan kinerja (kemungkinan dapat diabaikan) adalah pengoptimalan yang salah.
postfuturist
219

Seperti yang terlihat dalam dokumentasi , dari Django 1.8 ada bidang UUID bawaan. Perbedaan kinerja saat menggunakan UUID vs integer dapat diabaikan.

import uuid
from django.db import models

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

Anda juga dapat memeriksa jawaban ini untuk informasi lebih lanjut.

keithhackbarth
sumber
@Bagaimana kita menyetel django untuk menggunakan ini setiap saat ketika secara otomatis membuat ID untuk tabel?
anon58192932
3
@ anon58192932 Tidak terlalu jelas apa sebenarnya yang Anda maksud dengan "setiap saat". Jika Anda ingin UUID digunakan untuk setiap model, buat model dasar abstrak Anda sendiri dan gunakan sebagai pengganti django.models.Model.
Назар Топольський
4
Perbedaan kinerja hanya dapat diabaikan jika database yang mendasari mendukung tipe UUID. Django masih menggunakan sebuah charfield untuk kebanyakan DB (postgresql adalah satu-satunya db yang terdokumentasi untuk mendukung field UUID).
NirIzr
Saya bingung mengapa ini adalah jawaban yang populer ... Pertanyaannya adalah tentang kesulitan dengan paket pihak ketiga. Meskipun Django secara asli mendukung UUID, tampaknya masih ada sejumlah paket yang tidak memperhitungkan UUID. Menurut pengalaman saya, itu menyakitkan.
ambe5960
12

Saya mengalami situasi yang mirip dan menemukan dalam dokumentasi resmi Django , bahwa object_idtidak harus dari tipe yang sama dengan primary_key dari model terkait. Misalnya, jika Anda ingin hubungan generik Anda valid untuk ID IntegerField dan CharField , cukup setel Anda object_idmenjadi CharField . Karena bilangan bulat dapat dipaksakan menjadi string, itu akan baik-baik saja. Hal yang sama berlaku untuk UUIDField .

Contoh:

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)
Jordi
sumber
4

Masalah sebenarnya dengan UUID sebagai PK adalah fragmentasi disk dan degradasi sisipan yang terkait dengan pengidentifikasi non-numerik. Karena PK adalah indeks berkerumun, jika tidak bertambah secara otomatis, mesin DB Anda harus menggunakan drive fisik Anda saat memasukkan baris dengan id dengan ordinalitas lebih rendah, yang akan terjadi sepanjang waktu dengan UUID. Ketika Anda mendapatkan banyak data di DB Anda, mungkin perlu beberapa detik atau bahkan menit hanya untuk memasukkan satu record baru. Dan disk Anda pada akhirnya akan menjadi terfragmentasi, membutuhkan defragmentasi disk secara berkala. Ini semua sangat buruk.

Untuk mengatasinya, saya baru-baru ini membuat arsitektur berikut yang menurut saya layak untuk dibagikan.

UUID Pseudo-Primary-Key

Metode ini memungkinkan Anda memanfaatkan manfaat UUID sebagai Kunci Utama (menggunakan indeks UUID yang unik), sambil mempertahankan PK yang bertambah otomatis untuk mengatasi fragmentasi dan menyisipkan masalah penurunan kinerja karena PK non-numerik.

Bagaimana itu bekerja:

  1. Buat kunci utama yang bertambah otomatis yang disebut pkiddi Model DB Anda.
  2. Tambahkan idbidang UUID berindeks unik untuk memungkinkan Anda menelusuri berdasarkan id UUID, bukan kunci utama numerik.
  3. Arahkan ForeignKey ke UUID (menggunakan to_field='id') untuk memungkinkan kunci asing Anda mewakili Pseudo-PK dengan benar, bukan ID numerik.

Intinya, Anda akan melakukan hal berikut:

Pertama, buat Model Dasar Django abstrak

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

Pastikan untuk memperluas model dasar, bukan model.Model

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

Pastikan juga ForeignKeys Anda mengarah ke idbidang UUID, bukan pkidbidang yang bertambah otomatis :

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

Jika Anda menggunakan Django Rest Framework (DRF), pastikan juga untuk membuat kelas ViewSet Basis untuk menyetel bidang pencarian default:

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

Dan perluas itu sebagai ganti ModelViewSet dasar untuk tampilan API Anda:

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

Catatan lebih lanjut tentang mengapa dan bagaimana dalam artikel ini: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps

Steven Moseley
sumber
0

ini dapat dilakukan dengan menggunakan model abstrak dasar kustom, menggunakan langkah-langkah berikut.

Pertama buat folder di proyek Anda, beri nama basemodel, lalu tambahkan a abstractmodelbase.py dengan yang berikut ini:

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']

kedua: di semua file model Anda untuk setiap aplikasi lakukan ini

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) 

Jadi insiden model di atas melekat pada semua bidang dalam model baseabstract.

Fadipe Ayobami
sumber
-1

Pertanyaannya dapat dirumuskan ulang sebagai "adakah cara agar Django menggunakan UUID untuk semua id basis data dalam semua tabel alih-alih bilangan bulat yang bertambah otomatis?".

Tentu, saya bisa melakukan:

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

di semua tabel saya, tetapi saya tidak dapat menemukan cara untuk melakukan ini untuk:

  1. Modul pihak ketiga
  2. Django membuat tabel ManyToMany

Jadi, ini tampaknya menjadi fitur Django yang hilang.

EMS
sumber