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?

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top