Utilizzo di un GUID come chiave primaria

31

Generalmente uso gli ID di incremento automatico come chiavi primarie nei database. Sto cercando di scoprire i vantaggi dell'utilizzo di GUID. Ho letto questo articolo: link

Mi rendo conto che questi GUID sono utilizzati per identificare gli oggetti a livello di applicazione. Sono anche memorizzati come chiave primaria a livello di database. Ad esempio, supponiamo di avere la seguente classe:

public class Person
{
public GUID ID;
public string Name;
..

//Person Methods follow
}

Dire che volevo creare una nuova persona in memoria e quindi inserire la persona in un database. Posso solo fare questo:

Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

Supponiamo di avere un database contenente milioni e milioni di righe con un GUID come chiave primaria. Sarà sempre unico? Sto persino capendo correttamente i GUID?

Ho letto questo articolo in precedenza: link . Mi confonde un po 'perché sembra raccomandare un mezzo felice tra GUID e interi come chiavi primarie.

Modifica 11/06/18

Sono arrivato a credere che i Guids siano più adatti di quelli per le mie esigenze. Sto usando CQRS di più in questi giorni e GUID si adattano in modo più gradevole.

Ho notato che alcuni sviluppatori modellano i GUID come stringhe nel modello di dominio, ad es. qui: link - in questo caso: IdentityGuid è un GUID modellato come una stringa. C'è qualche ragione per farlo a parte ciò che è affermato qui: Utilizzare un oggetto valore personalizzato o un Guid come identificatore di entità in un sistema distribuito? . È "normale" modellare il GUID come stringa o dovrei modellarlo come GUID nel modello e nel database?

    
posta w0051977 02.08.2017 - 18:15
fonte

10 risposte

39

I GUID sono per definizione "IDentificatori univoci globali". C'è un concetto simile ma leggermente diverso in Java chiamato UUID "IDentificatori universali univoci". I nomi sono intercambiabili per tutti gli usi pratici.

I

GUID sono fondamentali per il modo in cui Microsoft ha previsto il funzionamento del cluster di database e, se è necessario incorporare i dati da fonti a volte connesse, aiutano davvero a prevenire le collisioni di dati.

Alcuni fatti Pro-GUID:

  • GUID prevengono le collisioni tra chiavi
  • I GUID aiutano a unire i dati tra reti, macchine, ecc.
  • SQL Server supporta la GUID semi-sequenziale per ridurre al minimo la frammentazione dell'indice ( ref , alcuni avvertimenti)

Alcuni brutti con GUID

  • Sono grandi, 16 byte ciascuno
  • Sono fuori servizio, quindi non puoi ordinare per ID e sperare di ottenere l'ordine di inserimento come puoi sugli ID autoincrementali
  • Sono più complicati da utilizzare, in particolare su insiemi di dati di piccole dimensioni (come le tabelle di ricerca)
  • La nuova implementazione GUID è più solida su SQL Server di quanto non sia nella libreria C # (puoi avere GUID sequenziali da SQL Server, in C # è casuale)

I GUID renderanno i tuoi indici più grandi, quindi il costo dello spazio su disco per l'indicizzazione di una colonna sarà più alto. I GUID casuali frammenteranno i tuoi indici.

Se sai che non sincronizzerai i dati da reti diverse, i GUID possono comportare un sovraccarico maggiore di quello che valgono.

Se hai bisogno di importare dati da client a volte connessi, possono essere molto più robusti per prevenire le collisioni tra le chiavi piuttosto che basarsi sull'impostazione degli intervalli di sequenza per quei client.

    
risposta data 03.08.2017 - 22:41
fonte
25

Will this always be unique?

Sempre? no, non sempre; è una sequenza finita di bit.

Say I had a database containing millions and millions of rows with a GUID as the Primary Key.

Milioni e milioni, probabilmente sei al sicuro. Un milione di milioni e la probabilità di una collisione diventa significativa. Ci sono buone notizie, però: hai già esaurito lo spazio su disco per il momento in cui ciò accade.

Can I just do this?

Puoi; non è una buona idea. Il tuo modello di dominio non dovrebbe normalmente generare numeri casuali; dovrebbero essere input per il tuo modello.

Oltre a ciò, quando hai a che fare con una rete inaffidabile, dove potresti ricevere messaggi duplicati, un UUID generato deterministicamente ti proteggerà dall'avere entità duplicate. Ma se assegni un nuovo numero casuale a ciascuno, allora hai più lavoro da fare per identificare la duplicazione.

Vedi la descrizione di uuid basata su nome in RFC 4122

Is it "normal" to model the GUID as a string or should I be modelling it as a GUID in the model and database?

Non penso che importi molto. Per la maggior parte del tuo modello di dominio, è un identificatore ; l'unica query che chiedi è se sia o meno la stessa di un altro identificatore. Il tuo modello di dominio normalmente non guarderà la rappresentazione in memoria di un identificatore.

Se GUID è disponibile come "tipo primitivo" nell'impostazione agnostica del tuo dominio, lo userei; consente al contesto di supporto di scegliere le ottimizzazioni appropriate che possono essere disponibili.

Ciò che dovresti riconoscere, tuttavia, è che la rappresentazione dell'identificatore, sia nella memoria che nell'archiviazione, è una decisione che prendi nella tua implementazione e pertanto dovresti prendere delle misure per assicurarti che la stampa del codice a piedi accoppiata a questa decisione è piccola - vedi Parnas 1972 .

    
risposta data 02.08.2017 - 19:07
fonte
10

Il GUID o UUID molto probabilmente essere unico per il modo in cui vengono generati e forniscono un modo sicuro per garantire l'unicità senza dover comunicare con un'autorità centrale.

Vantaggi dei GUID come chiave primaria:

  • È possibile copiare i dati tra diversi frammenti di un cluster e non è necessario preoccuparsi delle collisioni PK.
  • Ti consente di conoscere la tua chiave primaria prima di aver inserito qualsiasi record.
  • Semplifica la logica di transazione per l'inserimento di record figlio.
  • Non può essere facilmente indovinato.

Nell'esempio che hai fornito:

Person p1 = new Person();
p1.ID = GUID.NewGUID();
PersonRepository.Insert(p1);

Se si specifica il GUID prima del tempo di inserimento, è possibile salvare un round trip nel database quando si inseriscono record secondari secondari e consentire di eseguirne il commit nella stessa transazione.

Person p2 = new Person();
p2.ParentID = p1.ID
PersonRepository.Insert(p2);

Detrazioni ai GUID come chiave primaria:

  • Sono grandi 16 byte, il che significa che consumeranno più spazio quando vengono aggiunti indici e chiavi esterne.
  • Non si ordinano bene in quanto sono essenzialmente numeri casuali.
  • L'utilizzo dell'indice è molto, molto, molto cattivo.
  • Un sacco di movimento delle foglie
  • Sono difficili da ricordare.
  • Sono difficili da verbalizzare.
  • Possono rendere gli URL più difficili da leggere.

Se la tua applicazione non ha bisogno di sharding o clustering, sarebbe meglio attenersi a tipi di dati più piccoli e semplici come int o bigint.

Molti database hanno le loro implementazioni interne che tentano di attenuare i problemi di stoccaggio causati dal GUID di SQL Server e ha anche una funzione di newsequentialid per aiutare con l'ordinamento di UUID che consente un migliore utilizzo degli indici e generalmente hanno caratteristiche di performance migliori.

Inoltre, dal punto di vista di un tester, utente o sviluppatore che lavora con l'applicazione, l'utilizzo di un ID su un GUID migliorerà significativamente la comunicazione. Immagina di dover leggere un GUID su un telefono.

Alla fine, a meno che il clustering su larga scala o gli URL offuscanti siano un requisito, è più pragmatico attenersi agli ID con incremento automatico.

    
risposta data 02.08.2017 - 18:34
fonte
4

Direi di no, non utilizzare i GUID come chiavi primarie. Attualmente sto gestendo un DB di questo tipo e sono una delle cause principali dei problemi di prestazioni.

Gli extra 12 byte si sommano rapidamente; ricorda, molti PK saranno FK in altre tabelle e solo tre FK in una tabella ora hai 48 byte extra per ogni riga. Questo si aggiunge alla tabella e agli indici. Inoltre, si aggiunge all'I / O del disco. Questi 12 byte in più devono essere letti e scritti.

E se non si utilizzano i guai sequenziali e i PK sono raggruppati in cluster (che è ciò che accade per impostazione predefinita), SQL di volta in volta dovrà spostare intere pagine di dati per spremere di più nel "punto" giusto. Per un database altamente transazionale con molti inserimenti, aggiornamenti ed eliminazioni, le cose si impantanano velocemente.

Se hai bisogno di un qualche tipo di identificatore univoco per la sincronizzazione o qualcosa del genere, aggiungi una colonna guid. Basta non renderlo il PK.

    
risposta data 03.08.2017 - 02:14
fonte
2
Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

Questa è di gran lunga il motivo più importante per l'utilizzo dei GUID.

Il fatto che puoi creare un ID univoco senza che il tuo codice sia a conoscenza o che comunichi con il tuo livello di persistenza è un grande vantaggio.

Puoi essere sicuro che l'oggetto Person che hai appena generato sul tuo server, pc phone, laptop, dispositivo offline o qualsiasi cosa sia unica su tutti i tuoi server in ogni parte del mondo, comunque distribuiti.

Puoi incollarlo in qualsiasi tipo di database rdb o no-sql, file, inviarlo a qualsiasi webservice o buttarlo via immediatamente come non più necessario

No non otterrai mai una collisione.

Sì, gli inserimenti possono essere leggermente più lenti di quanto l'indice potrebbe dover essere manipolato con.

Sì, è più grande di un int.

  • modifica. dovevo sparare prima di finire.

So che molte persone si sentono strongmente in tema di autoincarn e questo è un argomento controverso con i DBA

Ma non posso davvero affermare con forza quanto siano superiori i guids. Dovresti utilizzare i GUID di predefinito in qualsiasi applicazione.

gli inserti automatici hanno molti difetti

  • Si utilizza un db distribuito No-Sql. Non puoi semplicemente parlare con tutte le altre istanze per scoprire qual è il numero successivo.

  • Si utilizza un sistema di code messaggi. Le cose hanno bisogno di ID prima che colpiscano il db

  • Stai creando diversi elementi e modificandoli prima di salvare. Ognuno ha bisogno di un id prima di colpire il db

  • Vuoi eliminare e reinserire le righe. Assicurati di non contare i tuoi ID auto inc ed esegui!

  • Non vuoi esporre quanti ordini hai preso quest'anno per ogni utente

  • Si desidera spostare i dati resi anonimi dalla produzione per testare e mantenere intatti i rapporti. Ma non eliminare tutti i dati di test esistenti.

  • Vuoi unire il tuo singolo tenant in un database multi-tenant ma tutti hanno un ordine 56.

  • Crei oggetti che sono persistenti ma effimeri. (ordini incompleti) di nuovo, non usare tutti i tuoi contenuti con cose che non esistono più.

La lista è infinita e sono tutti problemi reali che capitano alle persone in ogni momento. a differenza dell'esaurimento dello spazio su disco a causa di FK cols leggermente più grandi

Finalmente l'enorme problema con gli ints è ne hai esaurito !!! ok in teoria, non ce ne sono tanti. Ma in pratica lo fai perché le persone non li trattano come numeri casuali senza significato. fanno cose come

  • oh non voglio che i clienti pensino che siamo nuovi. inizia da 10.000

  • Ho dovuto importare un carico di dati, quindi ho aumentato il seed a 1m, quindi sappiamo cosa viene importato

  • abbiamo bisogno di categorie di dati. ogni periodo inizia al prossimo milione quindi possiamo usare le prime cifre come un numero magico

  • Ho eliminato e reimportato tutti i dati nuovamente con nuovi ID. Sì, anche i log di controllo.

  • usa questo numero, che è una chiave composta, come id di quest'altra cosa

risposta data 02.08.2017 - 20:14
fonte
2

I realise that these GUIDs are used to identify objects at the application level. Are they also stored as the primary key at the database level.

Ecco dove devi fermarti, proprio lì, e ripensare.

La chiave primaria del tuo database non dovrebbe MAI avere un significato commerciale. Dovrebbe essere privo di significato per definizione.

Quindi aggiungi il GUID come chiave aziendale e una normale chiave primaria (di solito una lunga int) come chiave primaria del database. È sempre possibile inserire un indice univoco sul GUID per garantire l'univocità.

Ovviamente sta parlando di teoria dei database, ma è anche una buona pratica. Mi sono occupato di database in cui le chiavi primarie avevano un significato commerciale (un cliente aveva pensato di salvare alcune risorse del database usandole come numeri dei dipendenti, numeri dei clienti, ecc. Ecc.) E porta sempre a problemi.

    
risposta data 03.08.2017 - 15:45
fonte
2

Usa sempre le chiavi primarie (PK) auto-incrementate generate dal database.

Perché utilizzare l'auto-incremento invece di GUID / UUID?

    I
  • GUID (UUID) non impediscono le collisioni tra chiavi poiché non sono univoci e non c'è modo di renderli unici poiché sono generati da numerose fonti.
  • I
  • GUID non aiutano la fusione poiché aumentano notevolmente il già lungo processo di unione con colonne PK e FK estremamente lunghe e non interi che richiedono molto tempo per l'elaborazione. Ricorda che per la maggior parte dei PK, ci sarà almeno un'altra tabella con almeno 2 chiavi della stessa dimensione: il proprio PK e un FK torneranno alla prima tabella. Tutti devono essere risolti in un'unione.

Ma come gestire quindi i frammenti, i cluster, ecc.?

  • Creazione di PK multi-colonna composti da colonne separate che identificano ciascun frammento / cluster / database / qualsiasi cosa che gestisce le proprie chiavi auto-incrementanti. Ad esempio ...

Un PK a 3 colonne per una tabella in cluster potrebbe essere ...

 DB | SH | KEY     |
----|----|---------|
 01 | 01 | 1234567 |

Ma che dire di ...?

  • Viaggi multipli nel database - La maggior parte delle applicazioni non ha bisogno di identificare univocamente un record creato fino a quando non viene inserito nel database poiché quel thread / sessione / qualsiasi cosa sta lavorando solo su uno alla volta. Se l'applicazione ha realmente bisogno di questa capacità, usa un'applicazione PK temporanea che non viene inviata al database . Lascia che il database inserisca il proprio PK di auto-incremento sulla riga quando viene inserito. Gli inserti utilizzeranno il PK temporaneo, mentre gli aggiornamenti e le eliminazioni utilizzeranno il PK permanente assegnato dal database.

  • Prestazioni - I computer possono elaborare interi semplici molto più velocemente di qualsiasi altra cosa a causa del dominio molto più grande se possibile valori per elemento in un GUID (37) rispetto a un intero (10). Ricorda anche che ogni carattere in un GUID deve prima essere convertito in un numero da manipolare dalla CPU.

Abusi comuni delle chiavi primarie I PK hanno un solo scopo ... identificare in modo univoco una riga in una tabella. Qualsiasi altra cosa è un abuso troppo comune.

Rilevamento di record mancanti

  • I record mancanti non possono essere rilevati osservando i PK. Bless QA per almeno il tentativo di garantire la qualità dei dati. Tuttavia, essi e la mancanza di comprensione da parte del programmatore di come vengono assegnate le chiavi nei moderni sistemi di database spesso li porta a credere che un numero mancante in un PK autoincrementante significhi dati mancanti. È non perché ...
  • Per le prestazioni, i sistemi di database allocano blocchi di numeri in "sequenze" (lotti, intervalli) per ridurre al minimo gli spostamenti nel database effettivo nella memoria. La dimensione di queste sequenze di numeri è spesso sotto il controllo del DBA, ma potrebbe non essere regolabile su una base per tabella.
  • Il takeaway della chiave è ... i numeri inutilizzati da queste sequenze non vengono mai restituiti al database, quindi ci sono sempre spazi nei numeri PK.
  • Perché ci sono numeri inutilizzati che chiedi? Poiché una serie di azioni di manutenzione del database può causare l'interruzione delle sequenze. Si tratta di riavvii, ricaricamenti di tabelle in blocco, alcuni tipi di ripristino da backup e altre operazioni.

L'ordinamento

  • L'ordinamento per PK è molto suscettibile agli errori poiché la maggior parte delle persone pensa che elenchi le righe nell'ordine in cui sono state create e quella che corrisponde all'ora dell'orologio. Principalmente, ma non necessariamente.
  • I motori di database sono ottimizzati per le massime prestazioni e ciò potrebbe significare ritardare l'inserimento dei risultati di una transazione complicata di lunga durata al fine di inserire brevi e semplici, "out-of-turn" per così dire.
risposta data 06.08.2017 - 06:53
fonte
1

Come qualsiasi cosa, ci sono vantaggi e svantaggi nel fare questo:

The Good:

  1. Le tue chiavi hanno sempre la stessa lunghezza (i database di grandi dimensioni possono avere chiavi molto grandi)

  2. L'unicità è praticamente garantita - anche quando le stai generando da un sistema separato e / o non hai letto l'ultimo ID dal database

The Bad:

  1. Come già detto molto sopra: indici e archivi di dati più grandi.

  2. Non puoi ordinare per ID, devi ordinare per qualcos'altro. Altri indici, probabilmente meno efficienti.

  3. Sono meno leggibili. Gli interi sono generalmente più facili da analizzare, ricordare e digitare per le persone. L'utilizzo di GUID come ID nelle clausole WHERE su più tabelle unite può far sciogliere la testa.

Come tutto, usali dove opportuno, non essere dogmatico - in molte situazioni i numeri interi con incremento automatico sono migliori, a volte i GUID sono fantastici.

    
risposta data 03.08.2017 - 17:24
fonte
0

Sì, puoi utilizzare GUID come chiave primaria. Il lato negativo è la dimensione e la rapida frammentazione dell'indice.

A meno che non sia necessario l'univocità tra i database (ad esempio un cluster) è preferibile l'intero.

    
risposta data 03.08.2017 - 15:52
fonte
0

Ecco la mia opinione su questo problema - la soluzione è una via di mezzo tra GUID e valori int, prendendo il meglio di entrambi.

La classe genera un valore Id pseudo casuale (ma crescente nel tempo), che è simile a un Comb GUID .

Il vantaggio chiave è che consente di generare valori Id sul client, piuttosto che utilizzare valori di incremento automatico generati sul server (che richiede un round trip) con un rischio quasi zero di valori duplicati.

I valori generati utilizzano solo 8 byte anziché 16 per un GUID e non dipendono da uno specifico ordine di ordinamento del database (ad esempio Server SQL per GUID ). I valori potrebbero essere espansi per utilizzare l'intero intervallo non firmato intero, ma ciò causerebbe problemi con qualsiasi database o altro repository di dati che ha solo tipi di interi con segno.

public static class LongIdGenerator
{
    // set the start date to an appropriate value for your implementation 
    // DO NOT change this once any application that uses this functionality is live, otherwise existing Id values will lose their implied date
    private static readonly DateTime PeriodStartDate = new DateTime(2017, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    private static readonly DateTime PeriodEndDate = PeriodStartDate.AddYears(100);
    private static readonly long PeriodStartTicks = PeriodStartDate.Ticks;
    private static readonly long PeriodEndTicks = PeriodEndDate.Ticks;
    private static readonly long TotalPeriodTicks = PeriodEndTicks - PeriodStartTicks;

    // ensures that generated Ids are always positve
    private const long SEQUENCE_PART_PERMUTATIONS = 0x7FFFFFFFFFFF; 

    private static readonly Random Random = new Random();

    private static readonly object Lock = new object();
    private static long _lastSequencePart;

    public static long GetNewId()
    {
        var sequencePart = GetSequenceValueForDateTime(DateTime.UtcNow);

        // extra check, just in case we manage to call GetNewId() twice before enough ticks have passed to increment the sequence 
        lock (Lock)
        {
            if (sequencePart <= _lastSequencePart)
                sequencePart = _lastSequencePart + 1;

            _lastSequencePart = sequencePart;
        }

        // shift so that the sequence part fills the most significant 6 bytes of the result value
        sequencePart = (sequencePart << 16);

        // randomize the lowest 2 bytes of the result, just in case two different client PCs call GetNewId() at exactly the same time
        var randomPart = Random.Next() & 0xFFFF;

        return sequencePart + randomPart;
    }

    // used if you want to generate an Id value for a historic time point (within the start and end dates)
    // there are no checks, compared to calls to GetNewId(), but the chances of colliding values are still almost zero
    public static long GetIdForDateTime(DateTime dt)
    {
        if (dt < PeriodStartDate || dt > PeriodStartDate)
            throw new ArgumentException($"value must be in the range {PeriodStartDate:dd MMM yyyy} - {PeriodEndDate:dd MMM yyyy}");

        var sequencePart = GetSequenceValueForDateTime(dt.ToUniversalTime());
        var randomPart = Random.Next() & 0xFFFF;
        return ( sequencePart << 16 ) + randomPart;
    }

    // Get a 6 byte sequence value from the specified date time - startDate => 0 --> endDate => 0x7FFFFFFFFFFF
    // For a 100 year time period, 1 unit of the sequence corresponds to about 0.022 ms
    private static long GetSequenceValueForDateTime(DateTime dt)
    {
        var ticksFromStart = dt.ToUniversalTime().Ticks - PeriodStartTicks;
        var proportionOfPeriod = (decimal)ticksFromStart / TotalPeriodTicks;
        var result = proportionOfPeriod * SEQUENCE_PART_PERMUTATIONS;
        return (long)result;
    }

    public static DateTime GetDateTimeForId(long value)
    {
        // strip off the random part - the two lowest bytes
        var timePart = value >> 16;
        var proportionOfTotalPeriod = (decimal) timePart / SEQUENCE_PART_PERMUTATIONS;
        var ticks = (long)(proportionOfTotalPeriod * TotalPeriodTicks);
        var result = PeriodStartDate.AddTicks(ticks);
        return result;
    }
}
    
risposta data 12.06.2018 - 15:15
fonte

Leggi altre domande sui tag