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 ;
As mentioned in my comments above, you should find that a transaction is sufficient for most needs; however, if you need to explicitly wait until the other call has completed, use GET_LOCK(str,timeout)
:
Tries to obtain a lock with a name given by the string
str
, using a timeout oftimeout
seconds. Returns1
if the lock was obtained successfully,0
if the attempt timed out (for example, because another client has previously locked the name), orNULL
if an error occurred (such as running out of memory or the thread was killed withmysqladmin kill
). If you have a lock obtained withGET_LOCK()
, it is released when you executeRELEASE_LOCK()
, execute a newGET_LOCK()
, or your connection terminates (either normally or abnormally). Locks obtained withGET_LOCK()
do not interact with transactions. That is, committing a transaction does not release any such locks obtained during the transaction.This function can be used to implement application locks or to simulate record locks. Names are locked on a server-wide basis. If a name has been locked by one client,
GET_LOCK()
blocks any request by another client for a lock with the same name. This enables clients that agree on a given lock name to use the name to perform cooperative advisory locking. But be aware that it also enables a client that is not among the set of cooperating clients to lock a name, either inadvertently or deliberately, and thus prevent any of the cooperating clients from locking that name. One way to reduce the likelihood of this is to use lock names that are database-specific or application-specific. For example, use lock names of the formdb_name.str
orapp_name.str
.mysql> SELECT GET_LOCK('lock1',10); -> 1 mysql> SELECT IS_FREE_LOCK('lock2'); -> 1 mysql> SELECT GET_LOCK('lock2',10); -> 1 mysql> SELECT RELEASE_LOCK('lock2'); -> 1 mysql> SELECT RELEASE_LOCK('lock1'); -> NULLThe second
RELEASE_LOCK()
call returnsNULL
because the lock'lock1'
was automatically released by the secondGET_LOCK()
call.If multiple clients are waiting for a lock, the order in which they will acquire it is undefined and depends on factors such as the thread library in use. In particular, applications should not assume that clients will acquire the lock in the same order that they issued the lock requests.
Note
Before MySQL 5.5.3, if a client attempts to acquire a lock that is already held by another client, it blocks according to thetimeout
argument. If the blocked client terminates, its thread does not die until the lock request times out.This function is unsafe for statement-based replication. Beginning with MySQL 5.5.1, a warning is logged if you use this function when
binlog_format
is set toSTATEMENT
. (Bug #47995)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With