Motivi per evitare grandi valori ID


17

Stiamo lavorando su un'applicazione Web, non ancora accessibile agli utenti. Il mio capo ha notato che i record appena creati ottengono un ID di oltre 10.000, anche se nella tabella sono presenti solo meno di 100 record. Supponeva che l'interfaccia web per qualche motivo crea oltre 100 volte più record temporanei di quelli effettivi (e li elimina) e che ciò può portarci a esaurire il range entro pochi mesi dal rilascio.

Non penso che abbia ragione sulla causa dell'inflazione della carta d'identità (la collega che può rispondere è in vacanza, quindi non lo sappiamo per certo), ma supponiamo che lo sia. Ha detto che odierebbe usare una colonna bigint e che vorrebbe che smettessimo di aumentare automaticamente la colonna ID e scrivere un codice sul lato server che sceglie il primo intero "non utilizzato" e lo usa come ID.

Sono uno studente di informatica con poca esperienza pratica, ricoprendo un ruolo di sviluppatore junior. Ha anni di esperienza nella gestione di tutti i database della nostra organizzazione e nella progettazione della maggior parte di essi. io credo che lei è errato in questo caso, che un ID bigint è nulla da temere, e che imitando la funzionalità DBMS odori di un antipattern. Ma non mi fido ancora del mio giudizio.

Quali sono gli argomenti a favore e contro ogni posizione? Quali cose brutte possono accadere se usiamo un bigint e quali sono i pericoli di reinventare la ruota funzionalità di autoincremento ? Esiste una terza soluzione migliore di una? Quali potrebbero essere le sue ragioni per voler evitare un'inflazione dei valori facciali ID? Sono interessato anche a conoscere ragioni pragmatiche - forse gli ID bigint funzionano in teoria, ma in pratica causano mal di testa?

Non è previsto che l'applicazione gestisca grandi quantità di dati. Dubito che raggiungerà i 10.000 record effettivi entro i prossimi anni.

Se fa la differenza, stiamo usando Microsoft SQL Server. L'applicazione è scritta in C # e utilizza Linq a SQL.

Aggiornare

Grazie, ho trovato interessanti le risposte e i commenti esistenti. Ma temo che tu abbia frainteso la mia domanda, quindi contengono ciò che volevo sapere.

Non sono davvero preoccupato per il vero motivo degli ID alti. Se non riusciamo a trovarlo da soli, potrei fare una domanda diversa. Quello che mi interessa è capire il processo decisionale in questo caso. Per questo, supponiamo che l'applicazione scriva 1000 record al giorno, quindi ne elimina 9999 . Sono quasi sicuro che non sia così, ma è quello in cui il mio capo ha creduto quando ha fatto la sua richiesta. Quindi, in queste ipotetiche circostanze, quali sarebbero i pro e i contro dell'utilizzo di bigint o della scrittura del nostro codice che assegnerà gli ID (in un modo che riutilizza gli ID dei record già eliminati, per garantire che non ci siano lacune)?

Per quanto riguarda il vero motivo, sospetto fortemente che ciò sia dovuto al fatto che una volta abbiamo scritto codice per importare dati da un altro database, come prova del concetto che una migrazione successiva può essere effettuata in una certa misura. Penso che il mio collega abbia effettivamente creato diverse migliaia di dischi durante l'importazione e successivamente li abbia cancellati. Devo confermare se questo fosse effettivamente il caso, ma se lo è, non è nemmeno necessario agire.


Vedi il post di SM Ahasan Habib su codeproject.com/Tips/668042/…
RLF

Puoi chiarire? I nuovi ID ottengono semplicemente valori> 10000? O è che i nuovi ID hanno lacune di 10000? E quanti ID sono stimati necessari nella vita futura delle app?
user2338816

1
Per quanto riguarda la ricerca del primo ID inutilizzato, nel libro di Bill Karwin "SQL Antipatterns" c'è un capitolo proprio su quello. Quindi sì, può sicuramente essere visto come un antipasto!
Thomas Padron-McCarthy,

Risposte:


24

Senza vedere il codice, è piuttosto difficile dire in modo conclusivo cosa sta succedendo. Tuttavia, molto probabilmente il IDENTITYvalore viene memorizzato nella cache, causando lacune nel valore dopo il riavvio di SQL Server. Vedi /programming/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server per alcune buone risposte e informazioni a riguardo.

Un INTcampo semplice può contenere valori fino a 2.147.483.647. Puoi effettivamente avviare il valore dell'identità da -2.147.483.648, fornendo 32 bit di valori completi. 4 miliardi di valori distinti. Dubito fortemente che finirai i valori da usare. Supponendo che la tua applicazione stia consumando 1.000 valori per ogni riga effettiva aggiunta, dovresti creare quasi 12.000 righe al giorno ogni giorno per rimanere senza ID in 6 mesi, supponendo che tu abbia iniziato il IDENTITYvalore a 0 e stavi usando un INT. Se stavi usando un BIGINT, dovresti aspettare 21 milioni di secoli prima di rimanere senza valori se scrivessi 12.000 righe al giorno, consumando 1.000 "valori" per riga.

Detto questo, se si desidera utilizzare BIGINTcome tipo di dati del campo identità, non c'è sicuramente nulla di sbagliato in questo. Questo ti darà a tutti gli effetti una scorta illimitata di valori da usare. La differenza di prestazioni tra un INT e un BIGINT è praticamente inesistente sul moderno hardware a 64 bit, ed è altamente preferibile rispetto, ad esempio, all'uso NEWID()di generare GUID.

Se si desidera gestire i propri valori per la colonna ID, è possibile creare una tabella di chiavi e fornire un modo piuttosto antiproiettile per farlo utilizzando uno dei metodi mostrati nelle risposte a questa domanda: Gestione dell'accesso simultaneo a una tabella di chiavi senza deadlock in SQL Server

L'altra opzione, supponendo che tu stia utilizzando SQL Server 2012+, sarebbe quella di utilizzare un SEQUENCEoggetto per ottenere i valori ID per la colonna. Tuttavia, è necessario configurare la sequenza per non memorizzare nella cache i valori. Per esempio:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;

In risposta alla percezione negativa del tuo capo di numeri "alti", direi che differenza fa? Supponendo che tu usi un INTcampo, con un IDENTITY, potresti infatti avviare IDENTITYat 2147483647e "incrementare" il valore di -1. Ciò non farebbe assolutamente alcuna differenza per il consumo di memoria, le prestazioni o lo spazio su disco utilizzato poiché un numero di 32 bit è 4 byte, non importa se lo è 0o 2147483647. 0in binario viene 00000000000000000000000000000000memorizzato in un INTcampo con segno a 32 bit . 2147483647è01111111111111111111111111111111- entrambi i numeri occupano esattamente la stessa quantità di spazio, sia in memoria che su disco, ed entrambi richiedono esattamente la stessa quantità di operazioni della CPU per l'elaborazione. È molto più importante ottenere il codice dell'applicazione progettato correttamente piuttosto che ossessionarsi sul numero effettivo memorizzato in un campo chiave.

Hai chiesto informazioni sui vantaggi e gli svantaggi di (a) utilizzando una colonna ID di capacità maggiore, ad esempio a BIGINT, oppure (b) implementare la tua soluzione per evitare divari di ID. Per rispondere a queste preoccupazioni:

  1. BIGINTanziché INTcome tipo di dati per la colonna in questione. L'uso di a BIGINTrichiede il doppio della quantità di memoria, sia su disco che in memoria per la colonna stessa. Se la colonna è l'indice della chiave primaria per la tabella interessata, ogni indice non cluster collegato alla tabella memorizzerà anche il BIGINTvalore, a una dimensione doppia rispetto a un INT, sempre in memoria e su disco. SQL Server archivia i dati sul disco in pagine da 8 KB, in cui il numero di "righe" per "pagina" dipende dalla "larghezza" di ogni riga. Quindi, ad esempio, se si dispone di una tabella con 10 colonne, ognuna e una INT, si sarebbe in grado di memorizzare circa 160 righe per pagina. Se quelle colonne invece doveBIGINTcolonne, saresti in grado di memorizzare solo 80 righe per pagina. Per una tabella con un numero molto elevato di righe, ciò significa chiaramente che l'I / O richiesto per leggere e scrivere la tabella sarà doppio in questo esempio per un dato numero di righe. Certo, questo è un esempio piuttosto estremo - se avessi una riga composta da una singola INTo BIGINTcolonna e una singola NCHAR(4000)colonna, otterrai (semplicisticamente) una singola riga per pagina, sia che tu abbia usato unaINTo a BIGINT. In questo scenario, non farebbe molta differenza apprezzabile.

  2. Rotolare il proprio scenario per evitare lacune nella colonna ID. Dovresti scrivere il tuo codice in modo tale che la determinazione del valore ID "successivo" da utilizzare non sia in conflitto con altre azioni che si verificano nella tabella. SELECT TOP(1) [ID] FROM [schema].[table]Mi viene in mente qualcosa di simile all'ingenuità. Cosa succede se ci sono più attori che tentano di scrivere nuove righe sul tavolo contemporaneamente? Due attori potrebbero facilmente ottenere lo stesso valore, provocando un conflitto di scrittura. Per ovviare a questo problema è necessario serializzare l'accesso alla tabella, riducendo le prestazioni. Sono stati scritti molti articoli su questo problema; Lascio al lettore l'esecuzione di una ricerca su tale argomento.

La conclusione qui è: è necessario comprendere i propri requisiti e stimare correttamente sia il numero di righe, sia la larghezza delle righe, insieme ai requisiti di concorrenza dell'applicazione. Come al solito, Depends ™.


4
+1 ma non eliminerei i requisiti di spazio di BIGINT. Non tanto per lo spazio su disco ma piuttosto per l'I / O e lo spazio sprecato in memoria. Puoi compensare gran parte di questo utilizzando la compressione dei dati, quindi non senti davvero il peso del tipo BIGINT fino a quando non superi i 2 miliardi. Idealmente avrebbero semplicemente risolto il problema (esito a chiamarlo un bug di per sé) - mentre le persone non dovrebbero preoccuparsi degli spazi vuoti e mentre le persone non dovrebbero riavviare i loro server 15 volte al giorno, entrambi gli scenari sono abbastanza diffuso e spesso in tandem.
Aaron Bertrand

3
Punti molto validi, Aaron, come al solito. Tenderei comunque ad usare un INT, dato che BIGINT è praticamente un overkill totale a meno che non si aspettino un numero enorme di righe.
Max Vernon,

Un tipo di dati BIGINT per una colonna ID non avrà molto impatto sulla memoria a meno che tu non ne abbia centinaia o più in memoria contemporaneamente. Anche allora, è probabile che sia una piccola frazione della dimensione totale della riga.
user2338816

2
@ user2338816 questo è il punto: se la tabella diventa grande, ce ne saranno molti in memoria. E poiché la colonna identità è in genere la chiave di clustering, si tratta di ulteriori 4 byte per ogni singola riga in ogni indice. Importerà in ogni singolo caso? No. Dovrebbe essere ignorato? Assolutamente no. Nessuno sembra preoccuparsi della scalabilità fino a quando non è troppo tardi.
Aaron Bertrand

3
Anche se fare avere un legittimo affidamento che potrebbe essere necessario bigintprobabilmente te ringrazio per decidere che in anticipo, piuttosto che dover aggiungere questo a un tavolo con miliardi di righe.
Martin Smith,

6

Il compito principale da fare è trovare la causa principale per cui il valore corrente è così alto.

La spiegazione più ragionevole per le versioni di SQL Server precedenti a SQL2012, supponendo che si stia parlando di un database di test, sarebbe che ci fu un test di carico seguito da una pulizia.

A partire da SQL2012 il motivo più probabile è dovuto a diversi riavvii di SQL Engine (come spiegato nel primo link Max fornito).

Se il divario è causato da uno scenario di test, non c'è motivo di preoccuparmi dal mio punto di vista. Ma per essere al sicuro, verificherei i valori di identità durante il normale utilizzo dell'applicazione, nonché prima e dopo il riavvio del motore.

È "divertente" che MS affermi che entrambe le alternative (o il flag di traccia 272 o il nuovo oggetto SEQUENCE) potrebbero influire sulle prestazioni.

Potrebbe essere la soluzione migliore per usare BIGINT invece di INT solo per essere al sicuro per coprire i prossimi "miglioramenti" della SM ...


Probabilmente ho formulato la mia domanda nel modo sbagliato, ma non sono molto interessato a trovare la causa. C'è un'alta probabilità che si tratti di qualcosa che non verrà più visualizzato (risultati di un'esecuzione di test) o di una cattiva decisione di progettazione nell'applicazione, che può essere risolta al di fuori del database. Il punto era capire perché un DBA con esperienza considerasse gli ID alti cattivi o peggiori della gestione del nostro ID personale.
Rumtscho,

2

Rumtscho, se stai creando solo 1000 righe al giorno, c'è poco da decidere: usa il tipo di dati INT con un campo Identità e finisci con esso. La matematica semplice dice che se dai alla tua app un ciclo di vita di 30 anni (improbabile) potresti avere 200.000 righe al giorno ed essere ancora nell'intervallo di numeri positivi di un tipo di dati INT.

L'uso di BigInt è eccessivo nel tuo caso, può anche causare problemi se si accede all'app o ai dati tramite ODBC (come portato in Excel o MS Access, ecc.), Bigint non si traduce bene sulla maggior parte dei driver ODBC in app desktop.

Per quanto riguarda i GUID, a parte lo spazio su disco aggiuntivo e l'I / O extra, c'è l'enorme problema che non sono di progettazione sequenziale, quindi se fanno parte di un indice ordinato, puoi indovinare che ogni inserto sta per è necessario ricorrere all'indice. --Jim


Un buon punto sui GUID, a meno che non si usi NEWSEQUENTIALID () - Sono ancora d'accordo, non c'è motivo di usarli apparentemente in questa domanda.
Max Vernon,

1

C'è un divario tra i valori usati? O i valori iniziali sono 10.000 e da allora tutti aggiungono 1? A volte se il numero verrà assegnato ai clienti, il numero iniziale è maggiore di zero, diciamo ad esempio 1500, quindi il cliente non si rende conto che il sistema è "nuovo".

Lo svantaggio di usare bigint invece di smallint è che, poiché bigint utilizza "più spazio su disco", quando leggi il disco leggi meno blocchi su disco per ogni disco. Se lo spazio per le righe è piccolo, questo può essere uno svantaggio, in caso contrario non importa molto. Inoltre, non importa molto se non si esegue una query per molte risorse contemporaneamente e se si dispone degli indici corretti.

E come detto in altre risposte, se ti preoccupi di rimanere senza indici, non dovresti preoccuparti, i piccoli possono gestire a meno che tu non abbia un'attività milionaria. Inventare un meccanismo per "recuperare gli ID" è costoso e aggiunge punti di errore e complessità al software.

Saluti


2
L'OP sta vedendo lacune al riavvio del servizio. Questo è a causa di questo problema . Inoltre, non credo che un piccolo problema sia un buon compromesso a breve termine per il lavoro che ci vorrà per risolverlo in seguito.
Aaron Bertrand

@AaronBertrand in realtà, temo che altri lo abbiano frainteso quando hanno suggerito questa possibilità. Sono abbastanza sicuro che questa non sia la causa dei numeri alti, ma anche se lo fosse, non stavo tentando di trovare la causa, ma di imparare quali argomenti ci possono essere a favore e contro le soluzioni proposte. Vedi il mio aggiornamento per i dettagli.
Rumtscho,

@rumtscho in realtà questa risposta evidenzia un buon punto anche se non affronta direttamente la tua domanda: "Inventare un meccanismo per" recuperare gli ID "è costoso e aggiunge punti di errore e complessità al software".
Doktor J,

@DoktorJ Sono d'accordo con te. Ero la persona che ha votato a favore della risposta :) Volevo solo chiarire l'incomprensione, ecco perché ho lasciato il mio primo commento.
Rumtscho,

1

Se fossi il tuo capo, sarei molto interessato alle ragioni dei valori ID inaspettatamente alti ... per come la vedo io, per ciascuno dei due scenari che hai delineato:

  1. Se i test precedenti hanno aumentato i valori di identità, quindi anche gli altri tuoi commenti sul numero previsto di record mi spingono a suggerire un tipo di chiave più piccolo. Francamente prenderei in considerazione anche se fosse possibile ripristinare la sequenza e rinumerare i record esistenti se il test fosse fuori carattere per l'attuale uso previsto della tabella (la maggior parte considererebbe questo eccessivo - "dipende").

  2. Se la maggior parte dei record scritti nella tabella vengono eliminati subito dopo, sarei propenso a prendere in considerazione l'utilizzo di due tabelle; una tabella temporanea in cui i record non vengono conservati a lungo termine e un altro in cui vengono conservati solo i record che creeremo in modo permanente. Ancora una volta, le tue aspettative per il numero di record a lungo termine mi suggeriscono l'uso di un tipo più piccolo per la tua colonna chiave, e pochi record al giorno difficilmente ti causeranno un problema di prestazioni per "spostare" un record da una tabella a un'altra simile uno. Ho il sospetto che non sia il tuo scenario, ma immagino che un sito web di shopping potrebbe preferire mantenere un Basket / BasketItem e quando un ordine viene effettivamente inserito i dati vengono spostati nel set Order / OrderItem.

Riassumere; a mio avviso i BIGINT non sono necessariamente da temere, ma sono francamente inutilmente grandi per molti scenari. Se la tabella non diventa mai grande, non ti accorgerai mai che c'è stato un eccesso nella scelta del tipo ... ma quando hai tabelle con milioni di righe e molte colonne FK che sono GRANDI quando avrebbero potuto essere più piccole, allora potresti desiderare che i tipi sono stati selezionati in modo più conservativo (considerare non solo le colonne chiave, ma tutte le colonne chiave anteriore e tutti i backup che conservi, e così via!). Lo spazio su disco non è sempre economico (considerare il disco SAN nelle posizioni gestite, ovvero lo spazio su disco è in affitto).

In sostanza, sto discutendo per un'attenta considerazione della selezione del tipo di dati sempre piuttosto che a volte . Non prevedi sempre correttamente i modelli di utilizzo, ma penso che prenderai decisioni migliori di regola, quindi supponendo sempre che "più grande è meglio". In generale, seleziono il tipo più piccolo che può contenere l'intervallo di valori richiesto e ragionevole e prenderò felicemente in considerazione INT, SMALLINT e persino TINYINT se ritengo che il valore possa rientrare in quel tipo nel prossimo futuro. È improbabile che i tipi più piccoli possano essere utilizzati con le colonne IDENTITY, ma possono tranquillamente essere utilizzati con le tabelle di ricerca in cui i valori chiave sono impostati manualmente.

Infine, le tecnologie utilizzate dalle persone possono influenzare notevolmente le loro aspettative e risposte. Alcuni strumenti hanno maggiori probabilità di causare lacune negli intervalli, ad esempio mediante intervalli di prenotazione anticipata di identità per processo. Al contrario, @DocSalvager suggerisce una sequenza udibile completa che sembra riflettere il punto di vista del tuo capo; Personalmente non ho mai richiesto un tale livello di autorità, sebbene la regola generale secondo cui le identità sono sequenziali e generalmente prive di lacune mi è stata spesso incredibilmente utile nelle situazioni di supporto e nell'analisi dei problemi.


1

quali sarebbero i pro e i contro dell'utilizzo di bigint o della scrittura del nostro codice che assegnerà gli ID (in un modo che riutilizza gli ID dei record già eliminati, per garantire che non ci siano lacune)?

Usando bigintcome identità e vivendo con le lacune:

  • è tutta funzionalità integrata
  • puoi essere sicuro che funzionerà immediatamente
  • sprecherà spazio poiché ti intdarebbe comunque dati di circa 2M giorni; più pagine dovranno essere lette e scritte; gli indici possono diventare più profondi. (A questi volumi, tuttavia, ciò non rappresenta una preoccupazione significativa).
  • una colonna chiave surrogata dovrebbe essere insignificante, quindi gli spazi vuoti sono OK. Se viene mostrato agli utenti e le lacune vengono interpretate come significative, allora stai sbagliando.

Crea il tuo:

  • il tuo team di sviluppo farà tutto il lavoro di sviluppo e correzione dei bug per sempre.
  • vuoi solo riempire gli spazi vuoti alla coda o anche al centro? Progettare decisioni su cui discutere.
  • ogni scrittura dovrà emettere forti blocchi per impedire che processi simultanei acquisiscano lo stesso nuovo ID o risolvano i conflitti post facto .
  • nel peggiore dei casi dovrai aggiornare ogni riga della tabella per colmare le lacune se rowid = 1 viene eliminato. Questo martellerà la concorrenza e le prestazioni, con tutti gli aggiornamenti a cascata delle chiavi esterne ecc.
  • riempimento di spazi vuoti pigro o desideroso? Cosa succede alla concorrenza mentre ciò accade?
  • dovrai leggere per il nuovo ID prima di ogni scrittura = carico aggiuntivo.
  • sarà necessario un indice nella colonna ID per un'efficace rilevazione del gap.

0

Se sei davvero preoccupato di colpire la soglia superiore di INT per i tuoi PK, prendi in considerazione l'utilizzo dei GUID. Sì, lo so che è 16 byte contro 4 byte, ma il disco è economico.

Ecco un buon resoconto di pro e contro.


4
+1 perché questa è una soluzione, ma vedi il commento di Aaron sulla risposta di Max per un motivo per cui "il disco è economico" non è un motivo per usare i GUID senza valutare attentamente le opzioni.
Jack Douglas,

1
Ecco una migliore scrittura da un esperto di indice e architettura di SQL Server piuttosto che da uno sviluppatore: sqlskills.com/blogs/kimberly/disk-space-is-cheap
Aaron Bertrand

Oh, e ovviamente attenzione alle divisioni di pagina da NEWID ()
Max Vernon,

1
Il mio capo sembra opporsi a valori elevati solo per il fatto che sembrano alti. Spero che questa domanda mi mostri più possibili obiezioni, ma se questo è uno dei suoi argomenti principali, probabilmente reagirebbe ancora più negativamente ai GUID.
Rumtscho,

1
@rumtscho Di 'al tuo capo che un numero surrogato è solo un numero insignificante (la "dimensione" del numero è irrilevante) e che le lacune in una sequenza sono naturali e in gran parte inevitabili.
Aaron Bertrand

0

Chiavi primarie RDBMS (colonna generalmente denominata "ID")
Non è possibile evitare spazi vuoti nelle colonne (campi) autoincrementanti RDBMS. Sono principalmente destinati alla creazione di PK unici. Per prestazioni, i principali prodotti assegnano questi in lotti, quindi i meccanismi di recupero automatico per vari problemi di funzionamento normale possono far sì che i numeri rimangano inutilizzati. E 'normale.

Sequenze ininterrotte
Quando è necessario un numero di sequenza ininterrotto, come spesso previsto dagli utenti, dovrebbe essere una colonna separata assegnata a livello di codice e non deve essere la PK. Pertanto, quei 1000 record possono avere tutti lo stesso numero in quella colonna.

Perché gli utenti vogliono sequenze ininterrotte?
I numeri di sequenza mancanti sono il segno più elementare di errore scoperto in qualsiasi tipo di controllo. Questo principio "Contabilità-101" è onnipresente. Tuttavia, ciò che funziona per un numero limitato di record gestiti a mano, presenta un grave problema se applicato a un numero molto elevato di record nei database ...

Il riutilizzo dei valori chiave per i record non correlati invalida il database L'
utilizzo del "primo numero intero non utilizzato" introduce la probabilità che ad un certo punto in futuro un numero venga riutilizzato per i record non correlati all'originale. Ciò rende il database inaffidabile come una rappresentazione accurata dei fatti. Questa è la ragione principale per cui i meccanismi di incremento automatico sono appositamente progettati per non riutilizzare mai un valore.

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.