I need to increment an integer in a SQL Server 2008 column.
Sounds like I should use an IDENTITY
column, but I need to increment separate counters for each of my customers. Think of an e-commerce site where each customer gets their own incrementing order number, starting with 1. The values must be unique (per customer).
For example,
Customer1 (Order #s 1,2,3,4,5...)
Customer2 (Order #s 1,2,3,4,5...)
Essentially, I will need to manually do the work of SQL's identity
function since the number of customers is unlimited and I need order #
counters for each of them.
I am quite comfortable doing:
BEGIN TRANSACTION
SELECT @NewOrderNumber = MAX(OrderNumber)+1 From Orders where CustomerID=@ID
INSERT INTO ORDERS VALUES (@NewOrderNumber, other order columns here)
COMMIT TRANSACTION
My problem is locking and concurrency concerns and assuring a unique value. It seems we need to lock with TABLOCKX
. But this is a high volume database and I can't just lock the whole Orders
table every time I need to do a SELECT MAX+1
process and insert a new order record.
But, if I don't lock the whole table, then I might not get a unique value for that customer. Because some of our order entry is done after-the-fact in batches by a multi-threaded Windows process, it is possible that 2 operations will be simultaneously wanting to insert a new order for the same customer.
So what locking methodology or technique will avoid deadlocks and still let me maintain unique incrementing order numbers PER customer?
I would introduce a table to keep last number per customer to query and update it in the same transaction with order generation.
Update lock on select will help to avoid race condition when two orders are placed concurrently by the same customer.
Similar, but more straightforward approach (inspired by Joachim Isaksson) update lock here is imposed by the first update.
...
You are trying to relate two completely different requirements.
Even if you got this working. What happens if Customer A has an ealier order deleted, are you going to renumber the all their existing records to keep them consecutive and starting from 1. Now that would be a locking a problem....
Give the record an identity (or possibly a guid) When you want a count, query for it, if you want row number (never seen the point of that myself), use rowno.
You do not need a an auto increementing order per customer, you don't want one, and without a massive amount of locking can't have one.
Lateral thinking time.
If you present
it doesn't mean (and in fact shouldn't mean) that the order keys are 1, 2 and 3, they can be anything as long as they fulfill a uniqueness requirement.
would it be possible to create a table with an IDENTITY field in for each customer, then you could insert a new record in to the customer's table and pull the value from that.
In SQL Server 2005 and later, this is best done atomically, without using any transactions or locking:
You could do this:
We are now only locking one Customer from the customers table and not all customers, whenever 2 people try to add an order for the same customer at the same time, whoever gets the lock on the customer first wins and the other person will have to wait.
If people are inserting orders for different customers, they won't get in each others way!
Here is how this would work:
The default transaction level, read committed, does not protect you against phantom reads. A phantom read is when another process inserts a row in between your
select
andinsert
:Even one level higher, repeatable read, doesn't protect you. Only the highest isolation level, serializable, protects against phantom reads.
So one solution is the highest isolation level:
Another solution is to use the tablockx, holdlock and updlock table hints to make sure only your transaction can modify the table. The first locks the table, the second keeps the lock until the end of the transaction, and the third grabs an
update
lock for the select, so it doesn't have to upgraded later.These queries will be quick if you have an index on
CustomerID
, so I wouldn't worry too much about concurrency, certainly not if you have less than 10 orders per minute.