Garantire numeri univoci da un database di SQL Server
-
20-09-2019 - |
Domanda
Ho un'applicazione che utilizza numeri di incidenti (tra altri tipi di numeri). Questi numeri sono memorizzati in una tabella chiamata "Number_Setup", che contiene il valore corrente del contatore.
Quando l'applicazione genera un nuovo incidente, tavolo è number_setup e ottiene la riga richiesta contatore del numero (i contatori possono essere ripristinati giornaliera, settimanale, ecc e sono memorizzati come int di). E poi incremenets il contatore e aggiorna la riga con il nuovo valore.
L'applicazione è multiutente (circa 100 utenti in qualsiasi momento, così come i posti di lavoro sql che corrono e afferrare 100 di record incidenti e numeri richiesta incidenti per ciascuno). La tabella incidente ha alcuni numeri di incidenti duplicati dove non dovrebbero essere duplicato.
Un proc memorizzato viene utilizzato per recuperare il contatore successivo.
SELECT @Counter = counter, @ShareId=share_id, @Id=id FROM Number_Setup WHERE LinkTo_ID=@LinkToId AND Counter_Type='I' IF isnull(@ShareId,0) > 0 BEGIN -- use parent counter SELECT @Counter = counter, @ID=id FROM Number_Setup WHERE Id=@ShareID END SELECT @NewCounter = @Counter + 1 UPDATE Number_Setup SET Counter = @NewCounter WHERE id=@Id
Ora ho circondato quel blocco con una transazione, ma non sono del tutto sicuro che' sarà al 100% risolvere il problema, in quanto penso che ci sia ancora blocchi condivisi, in modo che il contatore può essere letto in ogni caso.
Forse posso controllare che il contatore non è stato aggiornato, nella dichiarazione di aggiornamento
UPDATE Number_Setup SET Counter = @NewCounter WHERE Counter = @Counter IF @@ERROR = 0 AND @@ROWCOUNT > 0 COMMIT TRANSACTION ELSE ROLLBACK TRANSACTION
Sono sicuro che questo è un problema comune con i numeri delle fatture in applicazioni finanziarie, ecc
Non posso mettere la logica nel codice oe utilizzare bloccaggio a quel livello.
Ho anche bloccato a HOLDLOCK, ma non sono sicuro di esso dell'applicazione. Dovrebbe essere messo in due istruzioni SELECT?
Come posso garantire non vengono creati duplicati?
Soluzione
Il trucco è quello di fare l'aggiornamento del contatore e leggere in una singola operazione atomica:
UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter
WHERE id=@Id;
Questo però non assegna il nuovo contatore per @NewCounter, ma invece restituisce come un set di risultati al client. Se si dispone di assegnare, utilizzare una variabile tabella intermedia per l'uscita del nuovo contatore INTO:
declare @NewCounter int;
declare @tabCounter table (NewCounter int);
UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter)
WHERE id=@Id
SELECT @NewCounter = NewCounter FROM @tabCounter;
Questo risolve il problema di rendere l'incremento del contatore atomica. Hai ancora altre condizioni di gara nel procedimento in quanto il LinkTo_Id e share_id possono ancora essere aggiornate dopo il primo selezionato in modo da poter incrementare il contatore del link-to articolo errato, ma che non può essere risolto solo da questo esempio di codice in quanto dipende anche sul codice che aggiorna il actualy shared_id e / o LinkTo_Id.
A proposito si dovrebbe entrare nel habbit del nome vostri campi con custodia costante. Se sono chiamato costantemente allora deve usare il caso corrispondenza esatta in codice T-SQL. I vostri script eseguire bene ora solo perché si dispone di un server case insensitive regole di confronto, se si distribuisce su un server di raccolta maiuscole e minuscole e vostri script non corrispondono il caso esatto degli errori nomi dei campi / tavoli seguirà bizzeffe.
Altri suggerimenti
Hai provato a usare GUID invece di autoincrements come identificativo univoco?
Se avete l'ablity di modificare il vostro lavoro che ottiene mutiple record, vorrei cambiare il pensiero in modo che il contatore è una colonna di identità. Poi, quando si ottiene il record successivo si può solo fare un inserto e ottenere il @@ identità del tavolo. Ciò garantire che si ottiene il numero più grande. Si potrebbe anche avere a che fare un dbccReseed di azzerare il contatore invece di aggiornare la tabella quando si desidera ripristinare l'identità. L'unico problema è che devi avere a che fare 100 o giù di lì inserti come parte del vostro processo di SQL per ottenere un gruppo di identità. Questo può essere troppo in alto, ma utilizzando una colonna di identità è un modo garantito per ottenere numeri unici.
potrei mancare qualcosa, ma sembra che si sta cercando di reinventare la tecnologia che è già stato risolto da maggior parte dei database.
invece di leggere e di aggiornamento dalla colonna 'contatore' nella tabella Number_Setup, perché non è sufficiente utilizzare una chiave primaria autoincrementing per il vostro contatore? Non avrete mai un valore duplicato per una chiave primaria.