2009-10-08 7 views
1

J'ai une application qui utilise des numéros d'incidents (parmi d'autres types de numéros). Ces numéros sont stockés dans une table appelée "Number_Setup", qui contient la valeur actuelle du compteur. Lorsque l'application génère un nouvel incident, la table number_setup reçoit la ligne de compteur de nombre requise (les compteurs peuvent être réinitialisés quotidiennement, hebdomadairement, etc. et sont stockés comme des int). Il incrémente ensuite le compteur et met à jour la ligne avec la nouvelle valeur.Assurer des numéros uniques à partir d'une base de données de serveur SQL

L'application est multi-utilisateur (environ 100 utilisateurs à la fois, ainsi que des jobs SQL qui exécutent et capturent 100 enregistrements d'incidents et demandent des numéros d'incidents pour chacun). La table des incidents comporte des numéros d'incident en double où ils ne doivent pas être dupliqués.

Un proc stocké est utilisé pour récupérer le compteur suivant.

 
SELECT @Counter = counter, @ShareId=share_id, @Id=id 
FROM Number_Setup 
WHERE [email protected] 
AND Counter_Type='I' 

IF isnull(@ShareId,0) > 0 
BEGIN 
    -- use parent counter 
    SELECT @Counter = counter, @ID=id 
    FROM Number_Setup 
    WHERE [email protected] 
END 

SELECT @NewCounter = @Counter + 1 

UPDATE Number_Setup SET Counter = @NewCounter 
WHERE [email protected] 

Je suis maintenant entouré ce bloc avec une transaction, mais je ne suis pas tout à fait sûr que ce sera 100% de résoudre le problème, car je pense qu'il ya des verrous encore partagés, de sorte que le compteur peut être lu de toute façon.

Peut-être que je peux vérifier que le compteur n'a pas été mis à jour, dans la déclaration de mise à jour

 
UPDATE Number_Setup SET Counter = @NewCounter 
WHERE Counter = @Counter 
IF @@ERROR = 0 AND @@ROWCOUNT > 0 
    COMMIT TRANSACTION 
ELSE 
    ROLLBACK TRANSACTION 

Je suis sûr que cela est un problème commun avec les numéros de facture dans les applications financières, etc.
Je ne peux pas mettre la logique dans le code soit et utilisez le verrouillage à ce niveau. J'ai également verrouillé à HOLDLOCK mais je ne suis pas sûr de son application. Devrait-il être mis sur les deux instructions SELECT?

Comment puis-je m'assurer qu'aucun doublon n'est créé?

+0

quelle version de Sql Server? – RBarryYoung

+0

en ce moment, 2000. –

Répondre

4

L'astuce est de faire la mise à jour du compteur et lu en une seule opération atomique:

UPDATE Number_Setup SET Counter = Counter+1 
OUTPUT INSERTED.Counter 
WHERE [email protected]; 

Ce bien n'attribue pas le nouveau compteur à @NewCounter, mais il retourne à la place un jeu de résultats au client .Si vous devez assigner, utiliser une variable de table intermédiaire pour la sortie du nouveau compteur en:

declare @NewCounter int; 
declare @tabCounter table (NewCounter int); 
UPDATE Number_Setup SET Counter = Counter+1 
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter) 
WHERE [email protected] 
SELECT @NewCounter = NewCounter FROM @tabCounter; 

Cela résout le problème de faire l'incrément du compteur atomique. Vous avez toujours d'autres conditions de concurrence dans votre procédure car LinkTo_Id et share_id peuvent toujours être mis à jour après la première sélection afin que vous puissiez incrémenter le compteur du mauvais lien vers l'élément, mais cela ne peut pas être résolu uniquement à partir de cet exemple de code. sur le code qui met à jour le shared_id et/ou LinkTo_Id.

BTW vous devriez entrer dans le habbit du nom de vos champs avec un cas constant. Si sont nommés de manière cohérente, doit utiliser le cas de correspondance exact dans le code T-SQL. Vos scripts s'exécutent correctement parce que vous avez un serveur de classement insensible à la casse, si vous déployez sur un serveur de classement sensible à la casse et que vos scripts ne correspondent pas au cas exact des erreurs de noms de champs/tables suivront à gogo.

+0

merci pour les commentaires. le schéma est en dehors de notre contrôle, et mon exemple a inventé des noms de champs et n'est pas une copie directe du sproc réel :) –

+0

Très utile, merci! –

0

avez-vous essayé d'utiliser des GUID au lieu des autoincrements comme identifiant unique?

0

Si vous avez la capacité de modifier votre travail qui reçoit plusieurs enregistrements, je changerais la façon de penser pour que votre compteur soit une colonne d'identité. Ensuite, quand vous obtenez l'enregistrement suivant, vous pouvez simplement faire un insert et obtenir l'identité @@ de la table. Cela assurerait que vous obtenez le plus grand nombre. Vous devrez également faire un dbccReseed pour réinitialiser le compteur au lieu de simplement mettre à jour la table lorsque vous souhaitez réinitialiser l'identité. Le seul problème est que vous devez faire une centaine d'insertions dans le cadre de votre travail SQL pour obtenir un groupe d'identités. Cela peut être trop lourd, mais l'utilisation d'une colonne d'identité est une façon garantie d'obtenir des nombres uniques.

+1

scope_identity() serait mieux que @@ identity pour vous assurer de récupérer l'identité que vous venez de créer. – adrianbanks

0

Il se peut que je manque quelque chose, mais il semble que vous essayez de réinventer une technologie qui a déjà été résolue par la plupart des bases de données. Au lieu de lire et de mettre à jour depuis la colonne 'Counter' dans la table Number_Setup, pourquoi n'utilisez-vous pas simplement une clé primaire auto-incrémentée pour votre compteur? Vous n'aurez jamais de valeur en double pour une clé primaire.

+0

car le compteur doit générer un nombre qui se réinitialise chaque jour, mois ou toute autre période définie par le client. différents clients ont des numéros d'incidents qui sont réinitialisés tous les jours et certains ne sont jamais réinitialisés. Le numéro de l'incident est placé dans un champ sur la table des incidents. ce champ n'est pas unique, simplement indexé. le proc stocké existant a été en place depuis plus de 10 ans :) –

Questions connexes