2009-04-22 9 views
0

J'ai une table de serveur SQL de clés de licence/numéros de série. La structure de la table est quelque chose comme;Affectation d'un numéro de série à un client à partir d'un groupe de numéros de série

[ 
RecordId int, 
LicenceKey string, 
Status int (available, locked, used, expired etc.) 
AssignedTo int (customerId) 
.... 
] 

Grâce à mon application ASP.NET, lorsque l'utilisateur décide d'acheter une licence en cliquant sur le bouton accepter, je dois réserver une clé de licence pour l'utilisateur. Mon approche est comme, Sélectionnez top 1 licenceKey à partir de KeysTable Où Status = disponible Mise à jour KeysTable Définir l'état = verrouillé puis renvoyer la clé à l'application.

Mon problème est que deux threads asp.net accèdent au même enregistrement et retournent la même clé de licence. Selon vous, quelle est la meilleure pratique pour effectuer de telles tâches? Y a-t-il une approche bien connue ou un modèle à ce genre de problème? Où utiliser les instructions lock() si j'en ai besoin? J'utilise Sql Server 2005, les procédures stockées pour l'accès aux données, une DataLayer, une BusinessLayer et une interface graphique Asp.Net.

Merci

Répondre

4

Il n'est probablement pas nécessaire d'utiliser des verrous ou des transactions explicites dans ce cas.

Dans votre procédure stockée, vous pouvez mettre à jour la table et récupérer la clé de licence en une seule opération atomique en utilisant une clause OUTPUT dans votre instruction UPDATE.

Quelque chose comme ceci:

UPDATE TOP (1) KeysTable 
SET Status = 'locked' 
OUTPUT INSERTED.LicenseKey 
-- if you want more than one column... 
-- OUTPUT INSERTED.RecordID, INSERTED.LicenseKey 
-- if you want all columns... 
-- OUTPUT INSERTED.* 
WHERE Status = 'available' 
+0

Que se passe-t-il si je veux renvoyer l'ensemble de l'enregistrement au lieu du champ licenceKey? Puis-je utiliser quelque chose comme ceci: Update TOP (1) KeysTable SET Status = "verrouillé" SELECT * FROM KeysTable OÙ licenceKeyId = INSERTED.licenceKeyId Merci – UmutKa

+0

Non, vous ne pouvez pas faire exactement. Je mettrai à jour ma réponse pour montrer comment c'est fait. – LukeH

+0

Très bien, mais malheureusement j'ai un déclencheur sur cette table. Donc ça ne me laisse pas utiliser OUTPUt sans INTO. Si j'utilise INTO alors cela sera-t-il encore une opération atomique et retournera-t-il le résultat attendu ou devrais-je utiliser l'isolation de transaction? – UmutKa

0

Je pense que vous devriez marquer effectivement la clé comme indisponible dans le même que vous interrogez pour procédure stockée, car sinon il y aura toujours une sorte de condition de course. Verrouiller manuellement les tables n'est pas une bonne pratique à mon humble avis. Si vous avez un processus par étapes (par exemple, comme la réservation de billets d'avion), vous pouvez introduire un concept de réservation d'une clé pour une période donnée (par exemple 30 minutes), de sorte que lorsque vous recherchez une nouvelle clé, vous le réservez en même temps. EDIT: Le verrouillage de la logique métier fonctionnerait probablement si vous pouviez garantir qu'un seul processus allait changer la base de données, mais il est préférable de le faire au niveau de la base de données, de préférence dans un seul proc stocké. Pour le faire correctement, vous devez définir le niveau de transaction et utiliser les transactions dans la base de données, comme l'a suggéré @Adam Robinson dans sa réponse.

+0

Cela ne traite pas son problème du tout ...Il effectue la sélection et la mise à jour dans le même lot, mais comme il n'a aucun type de verrou (comme avec une transaction sérialisable), rien ne garantit qu'un lot parallèle ne sélectionnera pas la même ligne. –

+0

En fait je prévois d'écrire une méthode statique comme GetNextAvailableKey dans la couche de gestion qui a un bloc lock() autour des méthodes d'accès aux données comme select key, update key etc. Ou peut-être devrais-je écrire un proc GetNextKey avec un niveau d'isolation SERIALIZABLE qui sélectionne, met à jour et retourne la clé et ajoute un bloc lock() dans la méthode d'appel? – UmutKa

+0

Désolé, j'étais un peu imprécis avec ma réponse. S'il vous plaît voir la modification. – Grzenio

1

Pour réaliser ce dont vous parlez, vous devez utiliser une transaction sérialisable. Pour ce faire, suivez ce modèle:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
GO 
BEGIN TRANSACTION 

--Execute select 
--Execute update 

COMMIT TRANSACTION 

Cependant, pourquoi avez-vous une table avec toutes les clés de licence possibles? Pourquoi ne pas avoir un algorithme de génération de clé, puis créer une nouvelle clé lorsqu'un utilisateur l'achète?

+0

Les clés de licence ne nous appartiennent pas. Nous revendons les clés. – UmutKa

0

Vous pouvez également essayer d'utiliser des verrous (en SQL) en plus des transactions, pour vérifier qu'un seul thread a accès à la fois.

Je crois qu'un verrou d'application peut être utile ici.

Questions connexes