9

我在mysql中有一个存储过程来执行需要同步的任务,这样如果两个应用程序调用存储过程,只有一个可以访问一段代码来执行任务,让另一个被阻塞直到第一个一个完成任务。

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 ;

因此,如果两个应用程序同时调用存储过程,则任务必须同步。

一种。但是Start TRANSACTIONCOMMIT没有同步执行。

湾。并且 LOCK TABLES tableA也不能在存储过程中使用以确保同步。

C。我试图在应用程序级别同步存储过程调用。我用了

boost_interprocess scoped_lock lock();

它在 boost 1.41 中运行良好

但是 boost 1.34 库不支持进程间锁定互斥锁,这在我的情况下是可用的。

有没有办法同步代码的存储过程部分,这样当同时进行两个调用时,一个在另一个被执行之前被阻塞?

(添加了以下)编辑的代码:给出一个想法,我想在存储过程的同步块中执行什么。

它获取最后分配的 id,并将其加一并检查它是否未用于其他“名称”记录。找到有效 id 后,更新最后分配的 id 记录表,然后将其与给定的“名称”相关联。

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 ;
4

2 回答 2

9

正如我在上面的评论中提到的,您应该发现一个事务足以满足大多数需求;但是,如果您需要明确等到另一个调用完成,请使用GET_LOCK(str,timeout)

尝试str使用由 string 指定的名称获取锁,超时时间为timeout秒。1如果锁定成功、0尝试超时(例如,因为另一个客户端先前已锁定名称)或NULL发生错误(例如内存不足或线程被 杀死),则返回mysqladmin kill。如果你有一个用 获得的锁GET_LOCK(),它会在你执行时被释放RELEASE_LOCK(),执行一个新的GET_LOCK(),或者你的连接终止(正常或异常)。获得的锁GET_LOCK()不与事务交互。也就是说,提交事务不会释放在事务期间获得的任何此类锁。

该函数可用于实现应用程序锁或模拟记录锁。名称在服务器范围内被锁定。如果一个名称已被一个客户端锁定,则GET_LOCK()阻止另一个客户端对同名锁定的任何请求。这使同意给定锁名称的客户端能够使用该名称来执行协作咨询锁定。但请注意,它还会使不在合作客户端集合中的客户端无意或故意锁定名称,从而防止任何合作客户端锁定该名称。降低这种可能性的一种方法是使用特定于数据库或特定于应用程序的锁名称。例如,使用形式为db_name.str或的锁名称app_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');
        -> 空

第二次RELEASE_LOCK()调用返回,因为第二次调用自动释放NULL了锁。'lock1'GET_LOCK()

如果多个客户端都在等待一个锁,它们获取锁的顺序是不确定的,取决于使用的线程库等因素。特别是,应用程序不应假定客户端将按照它们发出锁请求的相同顺序获取锁。

注意
在 MySQL 5.5.3 之前,如果一个客户端试图获取一个已经被另一个客户端持有的锁,它会根据timeout参数阻塞。如果被阻塞的客户端终止,它的线程在锁定请求超时之前不会死亡。

此函数对于基于语句的复制是不安全的。binlog_format从 MySQL 5.5.1 开始,如果在设置为时使用此函数,则会记录警告STATEMENT。(错误 #47995)

于 2012-05-12T10:18:11.293 回答
8

使用 START TRANSACTION 启动事务实际上并不会启动它。START TRANSACTION 之后的第一个表访问确实如此。打开事务也不是并发控制的手段。如果你只需要这个,你可以依赖 MySQL 通过GET_LOCK()RELEASE_LOCK()和其他一些相关函数提供的咨询锁系统。

这次通过事务实现并发控制的另一种方法是依赖排他行锁。由于SELECTInnoDB 中的语句是非锁定的,因此发出这样的查询会启动一个事务,但是它既不设置任何锁,也不尊重任何预先存在的锁。SELECT如果在同一信息(行)上存在较早的事务操作,则要使语句实际阻塞,您必须使用FOR UPDATE子句。例如:

START TRANSACTION;
SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID' FOR UPDATE;
...

通过这种结构,在明确告知执行锁定读取'NAME_ID'的语句之后,将永远不会有两个并发事务在同一个事务上运行。SELECT

于 2012-05-12T12:00:09.470 回答