Django modellerinde birincil anahtar olarak bir UUID kullanma (genel ilişkiler etkisi)


91

Birkaç nedenden ötürü ^, bazı Django modellerimde birincil anahtar olarak bir UUID kullanmak istiyorum. Bunu yaparsam, ContentType aracılığıyla genel ilişkileri kullanan "Contrib.comments", "django-voting" veya "django-tagging" gibi dış uygulamaları kullanmaya devam edebilecek miyim?

Örnek olarak "django-voting" kullanıldığında, Oylama modeli aşağıdaki gibi görünür:

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)

Bu uygulama, oylanmakta olan model için birincil anahtarın bir tam sayı olduğunu varsayıyor gibi görünüyor.

Yerleşik yorum uygulaması, tamsayı olmayan PK'leri işleyebilir gibi görünüyor, ancak:

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

Bu "tamsayı-PK varsayımlı" sorun, UUID'leri kullanmayı zorlaştıracak üçüncü taraf uygulamalar için yaygın bir durum mu? Ya da muhtemelen bu durumu yanlış mı yorumluyorum?

Django'da UUID'leri çok fazla soruna neden olmadan birincil anahtarlar olarak kullanmanın bir yolu var mı?


^ Nedenlerden bazıları: nesne sayılarını gizleme, url "kimlik taramasını" engelleme, çakışmayan nesneler oluşturmak için birden çok sunucu kullanma, ...

Yanıtlar:


56

Bir UUID birincil anahtarı, yalnızca genel ilişkilerde değil, genel olarak verimlilikle ilgili sorunlara da neden olacaktır: her yabancı anahtar, bir makine sözcüğünden önemli ölçüde daha pahalı olacaktır - hem depolamak hem de birleştirmek için.

Bununla birlikte, hiçbir şey UUID'nin birincil anahtar olmasını gerektirmez: modelinizi bir uuid alanı ile tamamlayarak onu ikincil bir anahtar yapın unique=True. Örtük birincil anahtarı normal olarak kullanın (sisteminizin içinde) ve UUID'yi harici tanımlayıcınız olarak kullanın.


16
Joe Holloway, buna gerek yok: UUID oluşturma işlevini alan olarak kolayca sağlayabilirsiniz default.
Pi Delport

4
Joe: Modelimde UUID'lerimi oluşturmak için django_extensions.db.fields.UUIDField kullanıyorum. Çok basit, alanımı şöyle tanımlıyorum: user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel: mitchf django_extensions.db.fields.UUIDFieldtarafından belirtildiği gibi kullandığınızda, Django-Güney geçişlerinde herhangi bir sorun yaşamayacaksınız - onun bahsettiği alan Güney göçleri için yerleşik desteğe sahiptir.
Tadeck

126
Korkunç cevap. Postgres, 64 bitlik bir makinede yalnızca 2 kelime olan yerel (128 bit) UUID'lere sahiptir, bu nedenle yerel 64 bit INT'den "önemli ölçüde daha pahalı" olmaz.
postfuturist

8
Piet, üzerinde bir btree indeksi olduğu göz önüne alındığında, verilen bir sorguda kaç tane karşılaştırma olacaktır? Çok değil. Ayrıca, memcmp çağrısının çoğu işletim sisteminde uyumlu ve optimize edileceğinden eminim. Soruların niteliğine göre söyleyebilirim değil mümkün (muhtemelen ihmal edilebilir) performans farklılıklarının yanlış optimizasyonu olduğu için UUID kullanarak.
postfuturist

219

Belgelerde görüldüğü gibi, Django 1.8'den itibaren yerleşik bir UUID alanı vardır. Bir UUID ile tamsayı kullanırken ortaya çıkan performans farklılıkları ihmal edilebilir.

import uuid
from django.db import models

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

Daha fazla bilgi için bu yanıtı da kontrol edebilirsiniz .


@Keithhackbarth django'yu tablolar için otomatik olarak ID'ler oluştururken bunu her seferinde kullanacak şekilde nasıl ayarlayabiliriz?
anon58192932

3
@ anon58192932 "Her seferinde" ile tam olarak ne demek istediğini tam olarak anlamadım. UUID'lerin her model için kullanılmasını istiyorsanız, kendi soyut temel modelinizi oluşturun ve django.models.Model yerine kullanın.
Назар Топольський

4
Performans farklılıkları yalnızca temel alınan veritabanı UUID türünü desteklediğinde ihmal edilebilir. Django hala çoğu DB için bir karakter alanı kullanmaktadır (postgresql, UUID alanını destekleyen tek belgelenmiş veri tabanıdır).
NirIzr

Bunun neden popüler bir cevap olduğu kafam karıştı ... Soru üçüncü parti paketlerle ilgili zorluklarla ilgili soruyordu. Django'nun UUID'yi yerel olarak desteklemesine rağmen, hala UUID'leri hesaba katmayan birkaç paket var gibi görünüyor. Tecrübelerime göre bu bir acı.
ambe5960

12

Benzer bir durumla karşılaştım ve resmi Django belgelerinde , ilgili modelin primary_key ile object_idaynı türde olması gerekmediğini öğrendim . İsterseniz Örneğin, jenerik ilişki ikisi için geçerli olması IntegerField ve Charfield sadece kümenizle, id en bir olmak Charfield . Tamsayılar dizeleri zorlayabildiğinden, sorun olmaz. Aynı şey UUIDField için de geçerli .object_id

Misal:

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

Bir PK olarak UUID ile ilgili gerçek sorun, sayısal olmayan tanımlayıcılarla ilişkili disk parçalanması ve insert indirgemesidir. PK kümelenmiş bir dizin olduğu için, otomatik olarak artırılmadığında, DB motorunuz, UUID'lerle her zaman gerçekleşecek olan, daha düşük sıra kimliğine sahip bir satır eklerken fiziksel sürücünüze başvurmak zorunda kalacaktır. Veritabanınızda çok fazla veri aldığınızda, yalnızca yeni bir kayıt eklemek birkaç saniye veya hatta dakika sürebilir. Ve sonunda diskiniz, periyodik disk birleştirme gerektirecek şekilde parçalanır. Bunların hepsi gerçekten kötü.

Bunları çözmek için, yakın zamanda paylaşmaya değer olduğunu düşündüğüm aşağıdaki mimariyi buldum.

UUID Sözde Birincil Anahtar

Bu yöntem, parçalanmayı gidermek ve sayısal olmayan bir PK'ye sahip olmanın performans düşüşü endişelerini eklemek için otomatik olarak artırılmış bir PK'yi korurken, bir UUID'nin Birincil Anahtar olarak (benzersiz bir dizin UUID kullanarak) avantajlarından yararlanmanıza olanak tanır.

Nasıl çalışır:

  1. pkidDB Modellerinizde çağrılan , otomatik olarak artan bir birincil anahtar oluşturun .
  2. idSayısal birincil anahtar yerine bir UUID kimliğine göre arama yapmanıza olanak sağlamak için benzersiz dizine alınmış bir UUID alanı ekleyin .
  3. to_field='id'Yabancı anahtarlarınızın sayısal kimlik yerine Pseudo-PK'yi doğru şekilde temsil etmesine izin vermek için ForeignKey'i UUID'ye (kullanarak ) işaret edin.

Esasen aşağıdakileri yapacaksınız:

İlk olarak, soyut bir Django Temel Modeli oluşturun

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

Modeller yerine temel modeli genişlettiğinizden emin olun.

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

Ayrıca, Yabancı Anahtarlarınızın idotomatik olarak artan pkidalan yerine UUID alanını gösterdiğinden emin olun :

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

Django Rest Framework (DRF) kullanıyorsanız, varsayılan arama alanını ayarlamak için bir Base ViewSet sınıfı da oluşturduğunuzdan emin olun:

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

Ve API görünümleriniz için temel ModelViewSet yerine bunu genişletin:

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

Bu makaledeki neden ve nasıl olduğuna dair daha fazla not: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps


0

bu, aşağıdaki adımlar kullanılarak özel bir temel soyut model kullanılarak yapılabilir.

Öncelikle projenizde bir klasör oluşturun ve bunu temel model olarak adlandırın, ardından aşağıdakileri içeren bir abstractmodelbase.py ekleyin:

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

ikinci: her uygulama için tüm model dosyanızda bunu yapın

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) 

Dolayısıyla, yukarıdaki model olayı temel soyut modeldeki tüm alanın doğasında mevcuttur.


-1

Soru, "Django'nun otomatik olarak artan bir tamsayı yerine tüm tablolardaki tüm veritabanı kimlikleri için bir UUID kullanmasını sağlamanın bir yolu var mı?" Şeklinde yeniden ifade edilebilir.

Tabii ki yapabilirim:

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

tüm tablolarımda, ancak bunu yapmanın bir yolunu bulamıyorum:

  1. 3. parti modüller
  2. Django ManyToMany tabloları oluşturdu

Yani bu, eksik bir Django özelliği gibi görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.