I did ask same question on stackoverflow, but no response, hence I thought database experts will help me to solve this.
I have following table similar to Oracle user_sequences
.
I have logic of sequence prefix/suffix something, but for simplicity, I'm skipping as matters less here.
create table my_seq(
min_value integer,
Max_value integer,
last_value integer,
increment_by tinyint,
customer_id integer);
Assume in current table there are two records.
insert into my_seq(min_value,max_value,last_value,increment_by,customer_id)
values(1,99999999,1,1,'foo#',1),(1,999999999,100,1,'foo#',2);
My foo table structure is like,
create table foo(id Auto_increment,foo_number varchar(20),customer_id integer)
;
Constrained:
I can't use MySQL AUTO_INCREMENT columns as foo
contains different customers data, and every customer could opt foo_number
auto generation or manual entry and there should be gap if customer opted for auto_generation. So customer=1 has opted for it, foo# should be 1,2,3,4 etc, no gaps are allowed.
So far so good, with auto increment logic that we have implemented if my app runs in single thread
. We generate foo_number
and populate in foo
table, along with other data points.
I simply do a query to get the next auto#.
select last_number from my_seq where customer_id=?;
reads the # and the update the record.
update my_seq set last_number=last_number+increment_by where customer_id=?;
Problem:
When multiple concurrent session tries the run select last_number from my_seq...
, it returns same foo_number
multiple times. Also, I can't enforce single thread in application because of application side limitation and performance bottleneck, hence need to solve it in database side.
Please suggest, how I could avoid duplicate numbers? Please help, thanks in advance.
I did google, many stackoverflow links suggests get_last_id()
, as you could see, I can't use it. Please suggest.
Best Answer
Possible solution.
Each thread which will obtain the values from "sequence generator" have some
customer_id
value it will use when ask for next value. Additionally, it generates some random and unique (guaranteed!) value when starting (if all threads works in one OS instance, it can be thread's PID, for example, if not, it can be PID+IP or something else - but with 100% no collision). This random allows to distinguish the threads used the samecustomer_id
value.You create the
my_seq
table as shown above:Additionally you create the table looks like
When a thread needs next sequence value, it tries to obtain it by the next code:
In that timepoint any parallel thread can obtain the same value... to avoid it, the threat tries to secure this value by:
Only one thread used the same
@customer_id
can insert the record to this table with generated value - all another will not because of unique key.And now the thread checks was the secure process for that value successfull:
If 1 record returns - the secure process is successfull, the thread can use obtained value, and it updates
my_seq
table:If 0 records returns - the secure process is unsuccessfull, obtained value was obtained, secured and used by another thread with the same
customer_id
... and this thread needs to repeat the whole process of generate sequence value.You may to assemble this operations into one stored procedure or function. Moreover, you can organize the cycle directly into SP which will execute till the next value obtained... or any amount of attempts performed to avoid infinite loop.
Or simply lock the
my_seq
table exclusive for read and write before first read and till last update - no parallel thread can do something to interfere in that case.