Divari imprevisti nella colonna IDENTITÀ


18

Sto provando a generare numeri di ordine di acquisto univoci che iniziano da 1 e aumentano di 1. Ho una tabella PONumber creata usando questo script:

CREATE TABLE [dbo].[PONumbers]
(
  [PONumberPK] [int] IDENTITY(1,1) NOT NULL,
  [NewPONo] [bit] NOT NULL,
  [DateInserted] [datetime] NOT NULL DEFAULT GETDATE(),
  CONSTRAINT [PONumbersPK] PRIMARY KEY CLUSTERED ([PONumberPK] ASC)    
);

E una procedura memorizzata creata usando questo script:

CREATE PROCEDURE [dbo].[GetPONumber] 
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO [dbo].[PONumbers]([NewPONo]) VALUES(1);
    SELECT SCOPE_IDENTITY() AS PONumber;
END

Al momento della creazione, funziona perfettamente. Quando viene eseguita la procedura memorizzata, inizia con il numero desiderato e aumenta di 1.

La cosa strana è che, se spengo o iberno il mio computer, la prossima volta che la procedura viene eseguita, la sequenza è avanzata di quasi 1000.

Vedi i risultati qui sotto:

Numeri PO

Puoi vedere che il numero è passato da 8 a 1002!

  • Perché sta succedendo?
  • Come posso garantire che i numeri non vengano saltati in quel modo?
  • Tutto ciò di cui ho bisogno è che SQL generi numeri che sono:
    • a) Unico garantito.
    • b) incremento della quantità desiderata.

Ammetto di non essere un esperto di SQL. Posso fraintendere cosa fa SCOPE_IDENTITY ()? Dovrei usare un approccio diverso? Ho esaminato le sequenze in SQL 2012+, ma Microsoft afferma che non sono garantite per essere uniche per impostazione predefinita.

Risposte:


25

Questo è un problema noto e previsto: il modo in cui le colonne IDENTITY sono gestite da SQL Server è cambiato in SQL Server 2012 ( alcuni retroscena ); per impostazione predefinita memorizzerà nella cache 1000 valori e se riavvii SQL Server, riavvii il server, esegui il failover, ecc. dovrà eliminare quei 1000 valori, perché non avrà un modo affidabile per sapere quanti di essi fossero effettivamente rilasciato. Questo è documentato qui . Esiste un flag di traccia che modifica questo comportamento in modo tale che ogni assegnazione IDENTITY venga registrata *, impedendo quegli spazi specifici (ma non spazi vuoti da rollback o eliminazioni); tuttavia, è importante notare che questo può essere piuttosto costoso in termini di prestazioni, quindi non parlerò nemmeno del flag di traccia specifico qui.

* (Personalmente, penso che questo sia un problema tecnico che potrebbe essere risolto in modo diverso, ma dal momento che non scrivo il motore, non posso cambiarlo.)

Per essere chiari su come funzionano IDENTITÀ e SEQUENZA:

  • Nessuno dei due è garantito per essere univoco (è necessario imporlo a livello di tabella, utilizzando una chiave primaria o un vincolo univoco)
  • Nessuno dei due è garantito come gapless (qualsiasi rollback o eliminazione, ad esempio, produrrà un gap, nonostante questo specifico problema)

L'unicità è facile da applicare. Evitare le lacune non lo è. Devi determinare quanto sia importante per te evitare queste lacune (in teoria, non dovresti preoccuparti affatto delle lacune, poiché i valori IDENTITÀ / SEQUENZA dovrebbero essere chiavi surrogate insignificanti). Se è molto importante, non dovresti usare nessuna delle due implementazioni, ma piuttosto lanciare il tuo generatore di sequenze serializzabile (vedi alcune idee qui , qui e qui ) - nota solo che ucciderà la concorrenza.

Un sacco di informazioni su questo "problema":


Questa risposta (tranne la parte "flag di traccia") si applica anche alla maggior parte degli altri database SQL (quelli che hanno comunque sequenze).
Mustaccio,

Grazie per la risposta. L'unicità è il singolo requisito più importante. Le lacune non sono un grosso problema, purché non siano grandi. es. passare da 1 a 4 sarebbe accettabile, ma da 4 a 1003 no.
Ege Ersoz,

1
Versione breve: i valori ID verranno utilizzati come numeri di ordine di acquisto. Il cliente esegue rapporti mensili e desidera essere in grado di dire rapidamente quanti PO sono stati inviati quel mese semplicemente guardando il numero PO. Quindi non possiamo avere l'incremento di ~ 1000 (c'è una manutenzione settimanale in cui tutti i server, incluso il server DB, vengono riavviati).
Ege Ersoz,

3
Perché non dai loro un rapporto molto semplice che utilizza solo ROW_NUMBER () OVER (PARTITION BY Month ORDER BY ID)? Ancora una volta, il numero ID dovrebbe essere insignificante, questo è un modo terribile per controllare quanti ordini sono stati presi. Cosa succede se nel codice è presente un bug che elimina 1000 righe o ripristina 275 transazioni o che 500 ordini vengono legittimamente annullati?
Aaron Bertrand

1
@Ege: "... dire quanti ... solo guardando il numero PO". I tuoi utenti saranno delusi. I valori di identità semplicemente non funzionano in questo modo, né voi (o loro) dovreste assumere tale ipotesi. Unico? Sì. Consecutivo? No. Il modo corretto di contare le OP presentate durante un mese è ... contare il numero di OP raccolte durante quel mese, in base a un campo Data [inalterabile] in ciascun record.
Phill W.

-4

Questo è un problema di SQL Server. Tutto ciò che puoi fare è ridimensionare la colonna.

elimina le voci con ID colonna errato. Ridistribuito l'identità della colonna. E quindi la voce successiva ha l'ID corretto.

Reseed Identity usando il seguente comando sql: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 è l'ultimo ID corretto


1
Cosa intendi con "elimina le voci"?
ypercubeᵀᴹ

2
Hmmm .. sembra che l'eliminazione delle voci potrebbe solo portare alla perdita di dati.
Michael Green,
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.