2011-10-08 54 views
1

下面的脚本每5秒调用一次。问题是,如果服务器响应较慢,“博客”中的一个条目可以连续选中两次,因为服务器还没有时间将“完成”设置为“1”。是否有行业标准(或任何你称之为)来防止这种情况发生?防止两次调用相同的脚本来选择相同的mysql行

$result = mysql_query("SELECT * FROM blogs WHERE done=0 LIMIT 1"); 
$rows = mysql_num_rows($result); //If there are no entries in with done set to 0, that means we've done them all; reset all entries to 0. 
if($rows == 0) 
{ 
    mysql_query("UPDATE blogs SET done=0 WHERE done=1"); 
} 
else 
{ 
    while($row = mysql_fetch_array($result)) 
    { 
     mysql_query("UPDATE blogs SET done=1 WHERE id=$row[id]"); 
     // Do stuff 
    } 
} 

认为我可以将其更改为

while($row = mysql_fetch_array($result)) 
{ 
     if($row['done'] == 1){ die; } 
     mysql_query("UPDATE blogs SET done=1 WHERE id=$row[id]"); 
     //Do stuff 
} 

但将真正解决这一问题?我会想象会有一个更好的方式,真正防止它发生,毫无疑问的影子。

+0

可能重复的[Processing action twice - how to prevent that?](http://stackoverflow.com/questions/6235654/processing-action-twice-how-to-prevent - 那) – genesis

+0

请问你用什么机制让脚本每5秒运行一次?这是什么代码? – Luke

+0

@Coulton只需在mysql数据库中的任意位置添加时间戳,并将其与PHP脚本中的当前时间进行比较。如果有超过5秒的差异,请运行它。如果没有,跑死;当然,你必须每隔5秒访问一次该网站才能正常工作,但有大量的3D派对服务可以做到这一点,甚至可以用cron作业。 – natli

回答

1
$result = mysql_query("SELECT * FROM blogs WHERE done=0 LIMIT 1"); 
$rows = mysql_num_rows($result); //If there are no entries in with done set to 0, that means we've done them all; reset all entries to 0. 
if($rows == 0) 
{ 
    mysql_query("UPDATE blogs SET done=0 WHERE done=1"); 
} 
else 
{ 
    while($row = mysql_fetch_array($result)) 
    { 
     mysql_query("UPDATE blogs SET done=1 WHERE id=$row[id] AND done=0"); 
     if(mysql_affected_rows() != 1) 
      die(); 
     // Do stuff 
    } 
} 
+0

感谢您的提示,如果我这样做,是否有绝对0%的机会出现问题?如在,这两个脚本可以*永远*达不到/同时做的东西? – natli

+0

假设MyISAM或InnoDB表和'id'是唯一的,每个语句都是原子的,所以是的:'done'永远不会再更新,当它已经是1时。 –

+0

真棒,谢谢! – natli

2

我本来想去交易。以下是另一个示例StackOverflow question

只是一个问题:如果服务器更慢会发生什么?例如,选择statament需要很长时间(如5秒),一旦它完成(返回0行),新的选择执行(返回1个或多个行)

MySQL documentation

+0

我会更多地阅读交易,我不完全理解这个问题,而且mysql文档对我来说总是看起来像中文。看起来不错,谢谢。但是,如果第一个select从未将任何“完成”字段设置为1,第二个select会如何查找行?如果你的意思是第二个select会在done = 1的时候找到行,而第一个脚本仍然在将其他行更新为1,那么这并不会造成伤害。我想我真的不明白你在说什么。 – natli

+0

哦,可能我做了一个错误的假设。我认为“完成”值也可以在其他地方修改(例如博客的所有者对其执行某种操作)。如果是这样,第一个选择会运行,用户会更新它,然后下一个选择将运行并返回不同的数据。只是一个并发问题,也许你没有 –

2

我认为最好的方法以防止选择相同的行使用SELECT GET_LOCK("lock_name");SELECT RELEASE_LOCK("lock_name");。当你从MySQL服务器获得锁定时,其他处理尝试获取锁定将等待锁定被释放。下面是一个示例实现:

<?php 
function getLock($lockName, $dbc) { 
    $query = "SELECT GET_LOCK('".$lockName."', 0)"; 
    $result = mysql_query($query, $dbc); 
    $lockResult = mysql_fetch_row($result); 
    $lockResult = $lockResult[0]; 
    return $lockResult == 1 ? true : false; 
} 

function releaseLock($lockName, $dbc) { 
    $query = "SELECT RELEASE_LOCK('".$lockName."')"; 
    $result = mysql_query($query, $dbc); 
} 

// CONNECT TO DATABASE 
$dbc = mysql_connect('localhost', 'root', ''); 
mysql_select_db('test', $dbc); 

$loopQueue = true; 
$rowsProcessed = 0; 

// MAIN QUEUE LOOP 
while ($loopQueue) { 

    // TRY UNTIL GETTING A LOCK 
    $queueLockName = 'queue_lock_1'; 
    while (getLock($queueLockName, $dbc) === true) { 

     // WE GOT THE LOCK, GET A QUEUE ROW WITH PENDING STATUS 
     $query = 'SELECT * FROM test WHERE status = 0 ORDER BY ID ASC LIMIT 1'; 
     $result = mysql_query($query, $dbc); 

     if (mysql_num_rows($result) < 1) { 
      // SINCE WE DON"T HAVE ANY QUEUE ROWS, RELEASE THE LOCK 
      releaseLock($queueLockName, $dbc); 
      // WE DONT NEED TO LOOP THE MAIN QUEUE ANYMORE SINCE WE DONT HAVE ANY QUEUE ROWS PENDING 
      $loopQueue = false; 
      // BREAK THIS LOOP 
      break; 
     } 

     // WE GOT THE QUEUE ROW, CONVERT IT TO ARRAY 
     $queueRowArray = mysql_fetch_assoc($result); 

     // UPDATE QUEUE ROW STATUS TO SENDING 
     $query = 'UPDATE test SET status = 1 WHERE id = '.$queueRowArray['id']; 
     mysql_query($query); 

     // RELEASE THE LOCK SO OTHER JOBS CAN GET QUEUE ROWS 
     releaseLock($queueLockName, $dbc); 

     // DO STUFF ... 

     // UPDATE QUEUE ROW STATUS TO PROCESSED 
     $query = 'UPDATE test SET status = 2 WHERE id = '.$queueRowArray['id']; 
     mysql_query($query); 

     $rowsProcessed++; 
    } 
} 

echo "\n\n".'process finished ('.$rowsProcessed.')'."\n\n"; 
+0

我是否正确理解这一点;锁会锁定整个数据库,而不仅仅是我们正在处理的1行?因为那意味着没有两个(或更多)进程可以并排运行,所以他们必须等待对方完成,浪费大量时间。它确实看起来很有趣。 – natli

+0

@natli通过'GET_LOCK'获取一个锁并不会锁定数据库或任何表。 'GET_LOCK'主要用于获取应用程序级锁,而不是数据库级锁。您可以在官方文档中找到更多信息:http://dev.mysql.com/doc/refman/5.0/zh-cn/miscellaneous-functions.html#function_get-lock – matte

+0

其实我自己在一个工作队列上使用这种方法, 12个进程始终在队列上工作,并且没有任何重复的处理,并且这是不可能的,因为当另一个进程获得锁定(直到它被该进程释放)时无法获得锁定。 – matte

相关问题