I have a stored procedure in mysql thats to perform a task that needs to be synchronized such that if two application calls the stored procedure, only one can access a section of code to perform the task, keeping the other one to get blocked until the first one finishes the task.
DELIMITER $$
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20))
BEGIN
DECLARE maxLen int default 0;
START TRANSACTION;
#the section of code that needs to be synchronized
COMMIT
END;
$$
DELIMITER ;
So, if two applications call the stored procedure simultaneously, the task has to be synchronized.
a. But Start TRANSACTION and COMMIT did NOT synchronize the execution.
b. And LOCK TABLES tableA can not be used in stored procedure to ensure the synchronization too.
c. I tried to synchronize the stored procedure call in application level. I used
boost_interprocess scoped_lock lock();
It worked perfectly fine in boost 1.41
But interprocess locking mutex is not supported in the boost 1.34 library, which is what is available in my case.
Is there a way to synchronize the stored procedure section of code such that when two calls are made simultaneously, one gets blocked before the other gets executed?
(added the following) edited code: to give an idea what I am trying to perform in the synchronized block of the stored procedure.
It gets the last assigned id, and increment it by one and check whether it is not used for someother 'name' record. When a valid id is found, update the last assigned id record table and then associate that with the 'name' given.
DELIMITER $$
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20))
BEGIN
DECLARE maxLen int default 0;
START TRANSACTION;
#the section of code that needs to be synchronized
SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID';
findid_loop:
LOOP
set lastid = lastid + 1;
#this is to check whether it was assigned with someother name before.
IF not EXISTS (SELECT 1 FROM user_name_id WHERE name_id = lastgenerated) then
set nameid = lastgenerated;
set found = true;
LEAVE findid_loop;
END IF;
#for loop limit check
IF (counter < loopLimit) then
set counter = counter + 1;
ITERATE findid_loop;
ELSE
#reached the limit and not found.
LEAVE findid_loop;
END IF;
END LOOP findid_loop;
#if a valid id, update last id and assign to name.
IF (found) THEN
#update the id.
update DB_last_id set lastid = nameid where key = 'NAME_ID';
insert into user_name_id values (nameid ,name);
ELSE
#return an empty string for the application to take action on the failure.
set nameid = '';
END IF;
#end transaction here.
COMMIT
END;
$$
DELIMITER ;
Starting a transaction with START TRANSACTION does not actually start it. The first table access following START TRANSACTION does. Opening a transaction isn't also a mean for concurrency control. If you need just that, you can rely on the advisory locks system MySQL provides through GET_LOCK()
, RELEASE_LOCK()
, and a few other related functions.
An alternative way to implement concurrency control, through transactions this time, would be by relying on exclusive row locks. Since SELECT
statements are non-locking in InnoDB, issuing such query starts a transaction, however it neither sets any locks nor respects any pre-existing ones. To have a SELECT
statement actually block if there is an earlier transaction operating on the same information (row), you have to use FOR UPDATE
clause. For example:
START TRANSACTION;
SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID' FOR UPDATE;
...
With this construction there will never be two concurrent transactions operating on the same 'NAME_ID'
past the SELECT
statement that was explicitly told to perform a locking read.