Background:
This is a multi-tenant application, so that a normal identity column will not work. All tables have a unique client identifier Clients.id. So each client can have many customers. This column is not included below for simplicity.
We want to generate a unique customer number starting at 1000.
We store the current (last) generated number in a table called Master
. Let's say Master.CustomerNumber
. So numbers will go 1001, 1002 etc. and the last one is stored there.
So each time we add a customer, we have a query that looks up the current value, increment it, and insert it in Customer.Number
.
NOTE: we are using SQL Server 2008. We have multiple servers in a cluster.
What is the best approach to insure that if two customers are added at the same time that each gets a unique customer number? Stored procedure, locking, CFLOCKING?
How do I insure that this process is 'single-threaded' and that the same number is not issued twice?
I do have a unique index on Customer.Number+Clients.id
. I am interested in the implementation of how to guarantee uniqueness when generated.
You want to use a
Sequence
, for example:and then possibly something like :
The documentation has more details.
I know this is little bit late, but still hope it helps you :)
We do have same situation over here... And we solved it by having a common table in a separate database, which contains only three columns (i.e. tablename, columnname and LastIndex). Now, we use a separate SP to always getting new number from this table with separate transaction (as it should always commit, regardless your main Insert function true/false). So, this will always return with new ID to any request, and that new Index will be used for inserting a record.
Let me know if you need any sample on this.
I have not reviewed the existing solutions because they are quite long and elaborate. Wouldn't the following be all you need?
This is correct under any isolation level and any index structure. The row holding the ID information will be U or X locked by the engine.
In case there never has been an ID generated this update statement will not do anything. You can solve that by using
MERGE
or by using control flow. I recommendMERGE
.Or, you insert a row whenever you create a new client. Set
LastGeneratedNumber = 1000 - 1
.There is no need to use stored procedures but you certainly can. There is almost no performance difference to executing this T-SQL as a batch from the application. Do what's more convenient to you.
If you make this T-SQL part of your main transaction the ID assignment will be transactional. It will potentially roll back and it will serialize customer creation. If you don't like that use a separate transaction. That way IDs might be lost, though. This is unavoidable in any solution.
A variation the
UPDATE
given above is:You could use one sequence per client. This requires that your application executes DDL. This can be awkward. Also, you cannot make ID generation transactional. There is less control. I would not recommend that but it's possible.
You could use following solution:
Usage (no gaps):
Note: if you don't use transactions to generate the new customer number and then to insert this values into
Customer
table then they could get gaps.How it works ?
and CustomerNumwill prevent any duplicatesREAD COMMITTED
) but also underREAD UNCOMMITTED
,REPETABLE READ
andSERIALIZABLE
, theUPDATE
statement require andX
lock. If we have two concurent SQL Server sessions (and transactions) which try to generate a new CustomerNum then first session will successfully get theX
lock on tenant row and the second session will have to wait till first session (and transaction) will end (COMMIT
orROLLBACK
). Note: I assumed that every session has one active transaction.X
lock behavior: this is possible because two [concurent]X
locks are incompatibles. See table bellow with "Requested mode" and [Granted mode]:dbo.[Master]
a tenant row with a new customer number..
First end note: to manage transactions and exceptions I would use SET XACT_ABORT ON and/or BEGIN TRAN ... END CATCH. Discussion about this topic is beyond the the purpose of this answer.
Second end note: see updated section "How it works ?" (bullet 3 and 4).