Quale può essere il rovescio della medaglia di avere sempre una singola colonna intera come chiave primaria?


18

All'interno di un'applicazione Web su cui sto lavorando, tutte le operazioni del database sono astratte utilizzando alcuni repository generici definiti su Entity Framework ORM.

Tuttavia, per avere un design semplice per i repository generici, tutte le tabelle coinvolte devono definire un numero intero univoco ( Int32in C #, intin SQL). Fino ad ora, questo è sempre stato il PK del tavolo e anche il IDENTITY.

Le chiavi esterne sono molto utilizzate e fanno riferimento a queste colonne intere. Sono richiesti sia per coerenza che per la generazione di proprietà di navigazione da parte dell'ORM.

Il livello applicazione esegue in genere le seguenti operazioni:

  • caricamento iniziale dei dati dalla tabella (*) -SELECT * FROM table
  • Aggiornamento -UPDATE table SET Col1 = Val1 WHERE Id = IdVal
  • Elimina -DELETE FROM table WHERE Id = IdVal
  • Inserisci -INSERT INTO table (cols) VALUES (...)

Operazioni meno frequenti:

  • Inserimento di massa - BULK INSERT ... into tableseguito (*) da tutto il caricamento dei dati (per recuperare identificatori generati)
  • Eliminazione di massa : si tratta di una normale operazione di eliminazione, ma "voluminosa" dal punto di vista di ORM:DELETE FROM table where OtherThanIdCol = SomeValue
  • Aggiornamento in blocco - questa è una normale operazione di aggiornamento, ma "ingombrante" dal punto di vista di ORM:UPDATE table SET SomeCol = SomeVal WHERE OtherThanIdCol = OtherValue

* tutte le tabelle di piccole dimensioni vengono memorizzate nella cache a livello di applicazione e quasi tutte SELECTsnon raggiungono il database. Un modello tipico è carico iniziale e un sacco di INSERTs, UPDATEs e DELETEs.

Sulla base dell'utilizzo corrente dell'applicazione, ci sono poche possibilità di raggiungere mai 100 milioni di record in una qualsiasi delle tabelle.

Domanda: Dal punto di vista di un DBA, ci sono problemi significativi che posso incontrare avendo questo limite di progettazione della tabella?

[MODIFICARE]

Dopo aver letto le risposte (grazie per l'ottimo feedback) e gli articoli di riferimento, mi sento come se dovessi aggiungere ulteriori dettagli:

  1. Specifiche attuali dell'applicazione - Non ho parlato dell'attuale applicazione Web, perché voglio capire se il modello può essere riutilizzato anche per altre applicazioni. Tuttavia, il mio caso particolare è un'applicazione che estrae molti metadati da un DWH. I dati di origine sono piuttosto disordinati (denormalizzati in modo strano, con alcune incongruenze, nessun identificatore naturale in molti casi ecc.) E la mia app sta generando entità separate e chiare. Inoltre, IDENTITYvengono visualizzati molti degli identificativi generati ( ), in modo che l'utente possa utilizzarli come chiavi aziendali. Questo, oltre a un massiccio refactoring del codice, esclude l'uso di GUID .

  2. "non dovrebbero essere l'unico modo per identificare in modo univoco una riga" (Aaron Bertrand ♦) - questo è un ottimo consiglio. Tutte le mie tabelle definiscono anche un VINCOLO UNICO per garantire che i duplicati aziendali non siano consentiti.

  3. Progettazione basata su app front-end e progettazione basata su database : la scelta del design è causata da questi fattori

    1. Limitazioni di Entity Framework : sono consentiti più PK di colonne, ma i loro valori non possono essere aggiornati

    2. Limitazioni personalizzate : disporre di un'unica chiave intera semplifica notevolmente le strutture di dati e il codice non SQL. Ad esempio: tutti gli elenchi di valori hanno una chiave intera e valori visualizzati. Ancora più importante, garantisce che qualsiasi tabella contrassegnata per la memorizzazione nella cache possa essere inserita in una Unique int key -> valuemappa.

  4. Query di selezione complesse : questo non accadrà quasi mai perché tutti i dati di tabelle di piccole dimensioni (<20-30K record) vengono memorizzati nella cache a livello dell'applicazione. Questo rende la vita un po 'più difficile quando si scrive il codice dell'applicazione (più difficile scrivere LINQ), ma il database è molto più bello:

    1. Visualizzazioni elenco : non genererà SELECTquery al caricamento (tutto viene memorizzato nella cache) o query simili a questa:

      SELECT allcolumns FROM BigTable WHERE filter1 IN (val1, val2) AND filter2 IN (val11, val12)

      Tutti gli altri valori richiesti vengono recuperati tramite ricerche nella cache (O (1)), quindi non verranno generate query complesse.

    2. Modifica visualizzazioni : genererà SELECTistruzioni come questa:

      SELECT allcolumns FROM BigTable WHERE PKId = value1

(tutti i filtri e i valori sono ints)


Potresti trovare questi post rilevanti, dal momento che alcuni aspetti logici, fisici e pratici sono discussi per quanto riguarda l'uso di colonne con valori surrogati generati dal sistema.
MDCCL,

Risposte:


19

Oltre allo spazio su disco aggiuntivo (e, a sua volta, utilizzo della memoria e I / O), non c'è davvero alcun danno nell'aggiunta di una colonna IDENTITY anche alle tabelle che non ne hanno bisogno (un esempio di una tabella che non ha bisogno di una colonna IDENTITY è una semplice tabella di giunzione, come associare un utente alle sue autorizzazioni).

Mi sconsiglio di aggiungerli ciecamente ad ogni singolo tavolo in un post di blog del 2010:

Ma le chiavi surrogate hanno casi d'uso validi - fai solo attenzione a non presumere che garantiscano l'unicità (che a volte è il motivo per cui vengono aggiunte - non dovrebbero essere l' unico modo per identificare in modo univoco una riga). Se è necessario utilizzare un framework ORM e il framework ORM richiede chiavi intere a colonna singola anche nei casi in cui la chiave reale non è un numero intero o non è una singola colonna o nessuno dei due, assicurarsi di definire vincoli / indici univoci anche per le tue chiavi reali.


Grazie per la risposta rapida. Sì, l'applicazione utilizza un ORM (EF). Non richiede chiavi di colonna a numero intero intero, ma ho introdotto questa restrizione per rendere alcune operazioni generiche molto più semplici (dal punto di vista del design). Inoltre, tutte le cache delle applicazioni memorizzano tutto nelle mappe (dizionari) per il recupero rapido per chiave e la chiave deve essere unica. Da allora, ho scelto gli ints rispetto alle guide, sono costretto a usare IDENTITY per qualsiasi tabella in cui inserisco. Per le tabelle con valori fissi, IDENTITY non è richiesto.
Alexei,

Penso che esistano alcuni casi che richiedono di evitare il controllo di unicità sulle chiavi naturali. Come qualcuno che lavora con i dati GIS, quello che viene subito in mente è dove la chiave naturale è solo la geometria stessa o la geometria più una chiave esterna. Cercare le cose con una geometria esatta sarà sempre impraticabile, quindi è improbabile che un vincolo di unicità su di essa possa aiutare molto e potrebbe avere degli svantaggi nelle prestazioni. Lo stesso potrebbe valere se parte della chiave naturale è una lunga colonna di testo. Ma sono d'accordo: ogniqualvolta pratico, sì, dovrebbe essere applicato un vincolo unico sulla chiave naturale.
jpmc26,

13

Dalla mia esperienza, il motivo principale e schiacciante di utilizzare un ID separato per ogni tabella è il seguente:

In quasi tutti i casi il mio cliente ha prestato giuramento di sangue nella fase del concepimento che un campo "naturale" esterno XYZBLARGH_IDrimarrà unico per sempre e non cambierà mai per una data entità, e non verrà mai riutilizzato, alla fine sono comparsi casi in cui il Le proprietà della chiave primaria sono state rotte. Semplicemente non funziona in questo modo.

Quindi, da un punto di vista DBA, le cose che rendono lento o gonfio un DB non sono certamente 4 byte (o altro) per riga, ma cose come indici errati o mancanti, riorganizzazioni di tabelle / indici dimenticate, parametri di regolazione RAM / tablespace errati , trascurando di usare le variabili bind e così via. Questi possono rallentare il DB di fattori di 10, 100, 10000 ... non una colonna ID aggiuntiva.

Quindi, anche se ci fosse un aspetto negativo tecnico e misurabile di avere un ulteriore 32 bit per riga, non si tratta di ottimizzare l'ID, ma se l'ID sarà essenziale ad un certo punto, che sarà più probabilmente no. E non ho intenzione di contare tutti i vantaggi "soft" di una posizione di sviluppo software (come il tuo esempio ORM, o il fatto che renda più semplice per gli sviluppatori software quando tutti gli ID di progettazione hanno lo stesso tipo di dati e così via) .

NB: si noti che non è necessario un ID separato per n:mle tabelle di associazione poiché per tali tabelle gli ID delle entità associate devono formare una chiave primaria. Un controesempio sarebbe una strana n:massociazione che consente a più associazioni tra le stesse due entità per qualsiasi motivo bizzarro - quelle allora avrebbero bisogno della propria colonna ID per creare un PK. Ci sono librerie ORM che non può gestire PKs a più colonne, però, in modo che sarebbe una ragione di essere indulgente con gli sviluppatori, se hanno di lavorare con tale libreria a.


2
"strana associazione n: m che consente molteplici associazioni tra le stesse due entità" MOLTO comune nella vita reale. Ad esempio una persona possiede un'auto, quindi i requisiti cambiano in recored quando la proprietà è iniziata e terminata, (una persona può vendere un'auto e ricomprarla in un secondo momento, e mandare in crash il tuo software ....)
Ian Ringrose

Sì, qualcosa del genere, @IanRingrose.
AnoE

6

Se si aggiunge invariabilmente una colonna aggiuntiva senza significato a ogni tabella e si fa riferimento solo a quelle colonne come chiavi esterne, si renderà quasi inevitabilmente il database più complesso e difficile da usare. Effettivamente rimuoverai i dati di interesse per gli utenti dagli attributi della chiave esterna e costringerai l'utente / l'applicazione a fare un ulteriore join per recuperare le stesse informazioni. Le query diventano più complesse, il lavoro dell'ottimizzatore diventa più difficile e le prestazioni possono risentirne.

Le tue tabelle saranno scarsamente popolate con dati "reali" di quanto non sarebbero stati altrimenti. Il database sarà quindi più difficile da comprendere e verificare. È inoltre possibile che sia difficile o impossibile applicare determinati utili vincoli (in cui i vincoli implicherebbero più attributi che non si trovano più nella stessa tabella).

Ti suggerirei di scegliere le tue chiavi più attentamente e di renderle intere solo se / quando hai buone ragioni per farlo. Basare i progetti del database su una buona analisi, integrità dei dati, praticità e risultati verificabili piuttosto che basarsi su regole dogmatiche.


1
Eppure molti sistemi hanno chiavi primarie intere sintetiche su ogni tabella (quasi ogni app Ruby on Rails mai scritta, per esempio), senza soffrire di tali problemi. Inoltre, non soffrono mai del problema di dover inviare le modifiche alle chiavi primarie (che non avrebbero mai dovuto accadere) a tutte le tabelle delle chiavi esterne.
David Aldridge,

2
La domanda poneva possibili svantaggi, quindi la mia risposta. Non nego che le chiavi surrogate possano avere senso se usate saggiamente. Ma ho visto tabelle con 3,4,5 (o più) chiavi esterne prive di significato che quindi richiedevano 3,4,5 o più join per ottenere risultati utili da esse. Un design più pragmatico potrebbe non aver richiesto alcun join.
nvogel,

1
Non sono convinto che sia l'esecuzione di tali query che sia il problema principale che le persone hanno con un tale design: è la scrittura della query a cui spesso si oppongono.
David Aldridge,

5

Nella mia esperienza con vari database, una chiave primaria Integer è sempre migliore delle applicazioni che non hanno alcuna chiave definita. O che hanno chiavi che uniscono mezza dozzina di colonne varchar in modi scomodi che non sono logici ... (sospiro)

Ho visto applicazioni che sono passate da PK interi a GUID. La ragione per farlo era perché in alcuni casi c'era la necessità di unire i dati da più database di origine. Gli sviluppatori hanno cambiato tutte le chiavi in ​​GUID in modo che le fusioni potessero avvenire senza timore di collisioni di dati, anche su tabelle che non facevano parte dell'unione (nel caso in cui quelle tabelle diventassero parte di una futura unione).

Direi che un PK intero non ti morderà se non prevedi di unire i dati da fonti separate o potresti avere dati che vanno oltre i tuoi limiti di dimensione intera - è tutto divertente e giochi finché non esaurisci lo spazio per gli inserti .

Dirò, tuttavia, che può avere senso impostare il tuo indice cluster su una colonna diversa dal tuo PK, se la tabella verrà interrogata più frequentemente in quel modo. Ma questo è un caso straordinario, soprattutto se la maggior parte degli aggiornamenti e delle selezioni si basa sui valori PK.


2
Sembra una terribile giustificazione per cambiare tutte le chiavi in ​​guide. Attualmente lavoro con un database che utilizza le guide per tutte le chiavi surrogate .. non è divertente.
Andy,

2
No. L'uso dei GUID non è divertente. Non mi piacciono, ma rispetto il loro valore in alcuni casi d'uso.
CaM,

2

Mettere da parte:

  • Le guerre di religione (surrogato di Google vs chiave naturale)
  • Il problema separato di quali indici cluster definire sulle tabelle
  • La fattibilità della memorizzazione nella cache di tutti i tuoi dati

A condizione che tu stia utilizzando la cancellazione / aggiornamento in blocco, se del caso, e disponga di indici per supportare tali operazioni, non penso che potresti avere problemi a causa dello standard PK che usi.
È possibile che se in seguito EF genererà query con join, ecc., Che non saranno così efficienti come sarebbero con un repository basato su chiavi naturali, ma non so abbastanza su quell'area da dire con certezza in entrambi i casi.


4
Non riesco a pensare a un singolo caso in cui un join su una chiave naturale sarebbe più efficiente di un join su un numero intero: non molte chiavi naturali possono avere dimensioni inferiori a 4 byte e, in caso affermativo, non possono esserci elementi univoci righe per fare la differenza materiale.
Aaron Bertrand

Per SQL competente, ottimizzabile, sono d'accordo, ma mi riferivo a possibili limitazioni dei generatori SQL. La mia unica esperienza in questo campo è stata quella di creare ampie vedute con cui EF potesse essere alimentato a cucchiaio - anche se è possibile che gli sviluppatori .net non sapessero abbastanza di EF, o che ci fossero altri motivi.
TH

@AaronBertrand Direi che l'unico modo in cui potrebbero essere più efficienti è se un join non fosse affatto necessario. L'unico posto in cui considero l'uso di chiavi naturali è con elenchi di codici standard come i codici valuta ISO4127 (che sono riconoscibili dall'uomo), e potrei usare GBP, EUR ecc. Come chiave esterna per una chiave primaria o alternativa sul codice valuta tavolo.
David Aldridge,

@David Naturalmente, stavo parlando dei casi in cui sono necessari dei join. Ci sono molti casi in cui non voglio proliferare la chiave naturale in tutte le tabelle correlate, perché le chiavi naturali possono cambiare e questa è una cosa dolorosa.
Aaron Bertrand

Hmmm, vedo come la mia risposta potrebbe essere fraintesa nel promuovere surrogate di chiavi esterne naturali. Per essere chiari, in realtà li ho menzionati solo perché a) ho letto la domanda di Alexei come "è un problema che non usiamo le chiavi naturali?", B) la domanda di conclusione di Alexei è iniziata con "dal punto di vista di un DBA" e io sentivo che avrei dovuto riconoscere che c'è più di una prospettiva ec) perché penso che le funzionalità ORM da usare dettino ampiamente la scelta (se in realtà può fare la differenza). Sono fermamente nel campo di chiavi straniere surrogato.
TH

2

Hai alcuni fattori per aiutarti a guidarti,

  1. Definizione e spec.

    Se qualcosa è definito come unico dal compito o dalle leggi della fisica, stai perdendo tempo con una chiave surrogata.

  2. Unicità.

    Per sanità mentale personale, join e funzionalità di database di livello superiore è necessario, (a) colonna unica, (b) serie unica di colonne

    Tutti gli schemi sufficientemente normalizzati (1NF) forniscono uno dei seguenti. In caso contrario, dovresti sempre crearne uno. Se hai un elenco di persone impostato come volontario domenica e include cognome e nome, ti consigliamo di sapere quando hai due Joe Bob.

  3. Implementazione e ottimizzazione.

    Un int tende ad essere un piccolo modulo di dati che è veloce per il confronto e l'uguaglianza. Confrontalo con una stringa Unicode le cui regole di confronto possono dipendere dalle impostazioni internazionali (posizione e lingua). La memorizzazione di un 4242 in una stringa ASCII / UTF8 è di 4 byte. Memorizzandolo come un numero intero si adatta in 2 byte.

Quindi, quando si tratta di aspetti negativi, hai alcuni fattori.

  1. Confusione e ambiguità.

    1. Il post sul blog di @Aaron Bertrand lo riassume bene. Non è auto-documentazione avere un OrderID in base alle specifiche e all'attività e quindi imporre un " OrderID " attraverso l'implementazione del database. A volte è necessario chiarirlo o creare una convenzione, ma è probabile che ciò crei confusione.
  2. Spazio.

    I numeri interi aggiungono ancora spazio alla riga. E, se non li stai usando, non c'è motivo.

  3. Clustering.

    Puoi ordinare i tuoi dati solo in un modo. Se imponi una chiave surrogata che non è necessaria, ti concentri in quel modo o nel modo della chiave naturale?


Pro e contro simpatici e brevi.
Alexei,

@Alexei grazie, considera di contrassegnarlo come scelto se soddisfa ciò che stai cercando. Oppure, per chiedere chiarimenti.
Evan Carroll,
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.