Utilizzo di un UUID come chiave primaria nei modelli Django (impatto sulle relazioni generiche)


91

Per una serie di motivi ^, vorrei utilizzare un UUID come chiave primaria in alcuni dei miei modelli Django. Se lo faccio, sarò ancora in grado di utilizzare app esterne come "contrib.comments", "django-voting" o "django-tagging" che utilizzano relazioni generiche tramite ContentType?

Usando "django-voting" come esempio, il modello di voto ha questo aspetto:

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)

Questa app sembra presumere che la chiave primaria per il modello su cui si vota sia un numero intero.

L'app per i commenti integrata sembra essere in grado di gestire PK non interi, tuttavia:

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

Questo problema "presunto PK intero" è una situazione comune per app di terze parti che renderebbero difficoltoso l'utilizzo degli UUID? O forse sto interpretando male questa situazione?

C'è un modo per utilizzare gli UUID come chiavi primarie in Django senza causare troppi problemi?


^ Alcuni dei motivi: nascondere il conteggio degli oggetti, impedire l'URL "id crawling", utilizzare più server per creare oggetti non in conflitto, ...

Risposte:


56

Una chiave primaria UUID causerà problemi non solo con le relazioni generiche, ma con l'efficienza in generale: ogni chiave esterna sarà significativamente più costosa, sia da memorizzare che da unire, di una parola macchina.

Tuttavia, nulla richiede che l'UUID sia la chiave primaria: basta renderla una chiave secondaria , integrando il modello con un campo uuid con unique=True. Usa normalmente la chiave primaria implicita (interna al tuo sistema) e usa l'UUID come identificatore esterno.


16
Joe Holloway, non è necessario: puoi semplicemente fornire la funzione di generazione dell'UUID come campo default.
Pi Delport

4
Joe: Uso django_extensions.db.fields.UUIDField per creare i miei UUID nel mio modello. È semplice, definisco il mio campo in questo modo: user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel: Quando usi django_extensions.db.fields.UUIDFieldcome menzionato da mitchf, non avrai problemi con le migrazioni Django-South - il campo da lui menzionato ha il supporto integrato per le migrazioni verso sud.
Tadeck

126
Terribile risposta. Postgres ha UUID nativi (128 bit) che sono solo 2 parole su una macchina a 64 bit, quindi non sarebbe "significativamente più costoso" di INT nativo a 64 bit.
postfuturista

8
Piet, dato che ha un indice btree, quanti confronti ci saranno su una data query? Non molti. Inoltre, sono sicuro che la chiamata memcmp sarà allineata e ottimizzata sulla maggior parte dei sistemi operativi. In base alla natura delle domande, direi che non utilizzare l'UUID a causa di possibili (probabilmente trascurabili) differenze di prestazioni è l'ottimizzazione sbagliata.
postfuturista

219

Come visto nella documentazione , da Django 1.8 c'è un campo UUID integrato. Le differenze di prestazioni quando si utilizza un UUID rispetto a un numero intero sono trascurabili.

import uuid
from django.db import models

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

Puoi anche controllare questa risposta per ulteriori informazioni.


@Keithhackbarth come facciamo a impostare django per usarlo ogni volta quando creiamo automaticamente ID per le tabelle?
anon58192932

3
@ anon58192932 Non è molto chiaro cosa intendi esattamente per "ogni volta". Se desideri utilizzare gli UUID per ogni modello, crea il tuo modello di base astratto e usalo al posto di django.models.Model.
Назар Топольський

4
Le differenze di prestazioni sono trascurabili solo quando il database sottostante supporta il tipo UUID. Django utilizza ancora un campo di caratteri per la maggior parte dei DB (postgresql è l'unico database documentato a supportare il campo UUID).
NirIzr

Sono confuso perché questa è una risposta popolare ... La domanda era sulla difficoltà con i pacchetti di terze parti. Nonostante Django supporti nativamente l'UUID, sembra esserci ancora un certo numero di pacchetti che non tengono conto degli UUID. Nella mia esperienza, è un dolore.
ambe5960

12

Mi sono imbattuto in una situazione simile e ho scoperto nella documentazione ufficiale di Django che object_idnon deve essere dello stesso tipo della chiave_primaria del modello correlato. Ad esempio, se desideri che la tua relazione generica sia valida sia per gli ID IntegerField che per CharField , imposta semplicemente il tuo object_idcome CharField . Dal momento che gli interi possono forzare in stringhe andrà bene. Lo stesso vale per UUIDField .

Esempio:

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

Il vero problema con l'UUID come PK è la frammentazione del disco e la degradazione degli inserti associati a identificatori non numerici. Poiché il PK è un indice cluster, quando non è incrementato automaticamente, il tuo motore DB dovrà ricorrere alla tua unità fisica quando inserisce una riga con un ID di ordinalità inferiore, cosa che accadrà sempre con gli UUID. Quando si ottengono molti dati nel database, potrebbero essere necessari molti secondi o addirittura minuti per inserire un nuovo record. E il tuo disco finirà per diventare frammentato, richiedendo una deframmentazione periodica del disco. È tutto davvero brutto.

Per risolvere questi problemi, di recente ho ideato la seguente architettura che pensavo valesse la pena condividere.

La chiave pseudo-primaria UUID

Questo metodo consente di sfruttare i vantaggi di un UUID come chiave primaria (utilizzando un UUID di indice univoco), mantenendo al contempo un PK autoincrementato per affrontare la frammentazione e inserire i problemi di riduzione delle prestazioni di avere un PK non numerico.

Come funziona:

  1. Crea una chiave primaria con incremento automatico chiamata pkidsui tuoi modelli di database.
  2. Aggiungi un idcampo UUID indicizzato univoco per consentirti di eseguire la ricerca in base a un ID UUID, invece di una chiave primaria numerica.
  3. Punta la ForeignKey sull'UUID (utilizzando to_field='id') per consentire alle tue chiavi esterne di rappresentare correttamente lo Pseudo-PK invece dell'ID numerico.

In sostanza, farai quanto segue:

Innanzitutto, crea un modello base Django astratto

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

Assicurati di estendere il modello base invece dei modelli

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

Assicurati inoltre che le tue chiavi esterne puntino al idcampo UUID invece che al pkidcampo a incremento automatico :

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

Se stai usando Django Rest Framework (DRF), assicurati di creare anche una classe Base ViewSet per impostare il campo di ricerca predefinito:

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

Ed estendilo invece del ModelViewSet di base per le tue viste API:

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

Altre note sul perché e sul come in questo articolo: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps


0

ciò può essere fatto utilizzando un modello astratto di base personalizzato, utilizzando i seguenti passaggi.

Per prima cosa crea una cartella nel tuo progetto chiamala basemodel quindi aggiungi un abstractmodelbase.py con quanto segue di seguito:

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

secondo: in tutti i tuoi file modello per ogni app fai questo

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) 

Quindi l'incidente del modello sopra è inerente a tutto il campo nel modello astratto di base.


-1

La domanda può essere riformulata come "esiste un modo per far sì che Django utilizzi un UUID per tutti gli ID di database in tutte le tabelle invece di un numero intero auto-incrementato?".

Certo, posso fare:

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

in tutte le mie tabelle, ma non riesco a trovare un modo per farlo per:

  1. Moduli di terze parti
  2. Django ha generato tabelle ManyToMany

Quindi, questa sembra essere una caratteristica Django mancante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.