views:

246

answers:

4

Hello,

I have a sql server table of licence keys/serial numbers. Table structure is something like;

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

Through my ASP.NET application, when the user decides to buy a licence clicking the accept button, i need to reserve a licence key for the user. My approach is like, Select top 1 licenceKey from KeysTable Where Status = available Update KeysTable Set status = locked then return the key back to the application.

My concern is, if two asp.net threads access the same record and returns the same licencekey. What do you think is the best practice of doing such assignments ? Is there a well known aproach or a pattern to this kind of problem ? Where to use lock() statements if i need any ?

I'm using Sql Server 2005, stored procedures for data access, a DataLayer a BusinessLayer and Asp.Net GUI.

Thanks

A: 

I think that you should actually mark the key as unavailable in the same stored proc that you are querying for it, because otherwise there will always be some sort of race condition. Manually locking tables is not a good practise IMHO.

If you have a two staged process (e.g. like booking airline tickets), you could introduce a concept of reserving a key for a specified period of time (e.g. 30 mins), so that when you query for a new key, you reserve it at the same time.

EDIT: Locking in business logic probably would work if you can guarantee that only one process is going to change the database, but it is much better to do it on the database level, preferably in a single stored proc. To do it correctly you have to set the transaction level and use transactions in the database, just as @Adam Robinson suggested in his answer.

Grzenio
This doesn't address his issue at all...he's performing the select and update in the same batch, but since he doesn't have any sort of lock (like with a serializable transaction) there's nothing guaranteeing that a parallel batch won't select the same row.
Adam Robinson
Actually I'm planning to write a static method like GetNextAvailableKey in the business layer which has a lock() block around the Data access methods like select key, update key etc. Or maybe i should write a GetNextKey stored proc with SERIALIZABLE isolation level which selects, updates and returns the key and add a lock() block in the calling method ?
UmutKa
Sorry, I was a bit imprecise with my answer. Please see the edit.
Grzenio
+1  A: 

To achieve what you're talking about, you'll want to use a serializable transaction. To do this, follow this pattern:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION

--Execute select
--Execute update

COMMIT TRANSACTION

However, why do you have a table with every possible license key? Why not have a key generation algorithm, then create a new key when a user purchases it?

Adam Robinson
The licence keys don't belong to us. We are reselling the keys.
UmutKa
A: 

You could also try using locks (in SQL) in addition to transactions, to verify that only one thread has access at a time.

I believe that an application lock may be of help here.

MasterMax1313
+2  A: 

There's probably no need to use explicit locks or transactions in this case.

In your stored procedure you can update the table and retrieve the license key in a single, atomic operation by using an OUTPUT clause in your UPDATE statement.

Something like this:

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'
LukeH
What if i want to return the whole record instead of only the licenceKey field ? Can i use something like this :Update TOP(1) KeysTable SET Status = "locked"SELECT * FROM KeysTable WHERE licenceKeyId = INSERTED.licenceKeyIdThanks
UmutKa
No, you can't do that exactly. I'll update my answer to show how it is done.
LukeH
Very nice, but unfortunately i have a trigger on this table. So it doesn't let me use OUTPUt without INTO. If i use INTO then will it still be an atomic operation and return the result that i expect or should i need to use transaction isolation ?
UmutKa
Not certain, but I think that it would probably still be atomic. After all, it's just a single statement. Presumably you could output into a table variable and then select from that.
LukeH