SQL - 如何避免为并发连接拾取同一行?

时间:2014-12-28 02:48:25

标签: php mysql sql

例如,我在mySQL中有一个这样的表:

    ID            Used
==========      =========
   1001            0
   1002            0
   1003            1
   1004            1
   1005            0

我有一个php文件,可能会被数百名用户同时访问,以便获得分配给这些用户的唯一ID。

在PHP中,基本思想是选择一个Used字段为0的行,然后为该行分配Used to 1。返回此行的ID。

这个概念有用吗?如果有许多用户同时通过PHP访问数据库,是否存在竞争条件以及如何避免PHP获取并更新同一行(因此将相同的ID返回给两个不同的用户)?

3 个答案:

答案 0 :(得分:2)

你有一个良好的开端,但我会添加一个时间戳的MORE列,或者只是一个像信号量令牌一样的数字列。以下是它的工作原理,从示例数据开始/调整。根据我的" LastSeq"假设ID已经被使用了一定次数。专栏名称只是为了澄清我的下一点。

ID      Used   LastSeq
======  =====  =======
1001    0      24
1002    0      41
1003    1      15
1004    1      52
1005    0      39

每个需要ID的用户,运行查询以获得随机排序的AVAILABLE,这样就不会向所有用户提供"可用"始终以完全相同的顺序插入相同的ID。

select YQT.* 
   from YourQueueTable YQT
   where YQT.Used = 0 
   order by RAND()

足够简单,为您提供1001,1002和1005的ID,是的,我们假设这是随机顺序。所以现在,你有10个人到你的网站,他们都在第一个位置返回1001。要确保只有一个获取ID,请通过基于ID和LASTSEQ发出的UPDATE运行测试,例如

在PHP中,从上面的查询为您的可用结果集创建一个循环... 然后运行此[sample]更新查询,直到获得一个实际返回1记录已成功更新状态的ID。

Update YourQueueTable
   set Used = 1,
       LastSeq = LastSeq +1
   where ID = 1001
     AND LastSeq = 24

如果结果是1条记录已更新,那么您最好使用该ID,这就是原因。你知道"二手"是0和它的最后一个序列。因此,如果您尝试将状态更新为1并更新您检索到的ID的lastSeq +1和基于检索时间的lastSeq,那么现在成功执行的任何人(例如:ID 1001,Seq 24)现在将将表更新为status = 1 AND lastSeq = 25.

这意味着如果第二个人试图为ID 1001设置= 1,它将失败,因为它们的LastSeq也是24,这将使WHERE子句失败。这将继续循环让用户尝试1002,然后1005,直到其中一个为您点击...

答案 1 :(得分:1)

如果您希望这样做,您需要做的第一件事就是保留行。您可以在更新中执行此操作,并且更新应该是安全的 - 尽管确切的语义取决于您是使用InnoDB还是MyISAM。因为您需要锁定和事务,所以您应该使用InnoDB来完成这项工作。

您可以通过将used设置为1来保留该行。您还可以使用带变量的技巧回读id

update table t
    set used = if(@id = id, 1, 1)
    where used = 0;

然后,您可以在@id中读取所需的值。这应该是安全的,因为InnoDB支持并发事务和锁定,并且更新应该是原子的。

答案 2 :(得分:0)

在您选择使用的行= 0和将该行的使用值更新为1的时间之间,您将遇到竞争条件。

解决竞争条件的一种方法是先运行更新

$process_id = uniq_id();
//update mytable set Used = '$process_id' where Used = 0 limit 1
//select id from mytable where Used = '$process_id'
//do something
//update mytable set Used = '1' where Used = '$process_id'