如何锁定一个条目?

时间:2016-06-09 21:31:27

标签: mysql sql

我从全局外观数据库移出到下一个sql查询。我希望它适用于下一个算法: 1)从表格签到中选择/锁定1个条目。 2)锁定并更新usage_flag,所以我不需要担心竞争状况。

            begin;
            SELECT GetDistance('newhaven', area) as distance, id = (SELECT @proxy_id := id) 
                from checkins
                WHERE last_checkin > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
                  AND active  = 1
                  AND offline = 0
                  AND usage_flag = 0
             ORDER BY distance ASC, RAND() limit 1 FOR UPDATE;
             UPDATE checkins set usage_flag=1 where id=@proxy_id;
             commit;

但是在生产一天之后,我看到了很多错误:

'Lock wait timeout exceeded; try restarting transaction'

我如何改进此代码,或者在我的猜想中完全错误select for update begin-commit如何工作。请纠正我或给我一些想法,让它更好。

2 个答案:

答案 0 :(得分:0)

您的查询基本上会锁定整个表,因为它需要所有行来计算订单。

我假设您的查询运行一段时间(否则您可能只是更新到位),因此您的表将被锁定,直到计算完所有距离为止。因此要么改善距离计算,要么在此期间不要锁定它。

为了最大限度地缩短锁定时间,您可以分离(耗时)选择和锁定更新。如果在选择期间状态发生了变化,您可以在锁外重做,然后重试。

你可以,例如使用以下程序

delimiter $$

create procedure SetUsageflag() 
begin
  set @LockCounter = 10;

  repeat 
    set @LockCounter = @LockCounter - 1;

    set @proxy_id = 
            (SELECT id from checkins
             WHERE last_checkin > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
                  AND active  = 1
                  AND offline = 0
                  AND usage_flag = 0
             ORDER BY GetDistance('newhaven', area) asc, RAND() 
             limit 1);

    start transaction;
    -- check if status is still 0, and lock that row 
    set @checkvalue = (select coalesce(usage_flag,0) 
                       from checkins where id = @proxy_id for update);
    if @checkvalue = 0 then
      update checkins set usage_flag = 1 where id = @proxy_id;
      set @LockCounter = 0;
      -- return result to caller
      SELECT GetDistance('newhaven', area) as distance, id 
      from checkins where id = @proxy_id;
    end if ;
    commit; 

    -- give up after some time
    if @LockCounter = 1 then 
      SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = 'Lock-Timeout trying to find an unused checkin!', MYSQL_ERRNO = 1205; 
    end if;

  until (@LockCounter <= 1) end repeat;
end $$

delimiter ;

如果您经常需要重做select,则可能需要使用临时表并添加例如如果第一个条目失败,那么10个最佳条目并一个接一个地测试它们,所以你不必重做计算(我不知道需要多长时间)。

答案 1 :(得分:0)

但是你有这种结构的方式,你需要锁定整个表格 选择订单限制1必须锁定表,因为更改可能会更改查询的输出

我怀疑这很贵

GetDistance('newhaven', area)

您可以将该输出存储在列

你真的需要什么?你只需要知道另一个进程没有设置usage_flag = 1?

我不知道mysql但是你可以得到一个行数吗?

UPDATE checkins set usage_flag=1 where id=@proxy_id and usage_flag=0;

如果另一个进程已将其更改为1,则此行的行数为0 这样可以避免(代价高昂的)交易