防止同时进行db表行访问

时间:2012-01-16 08:26:25

标签: php mysql timestamp file-locking simultaneous

有时我们的支持团队中的两个管理员正在尝试对db表行执行相同的敏感操作(比方说,修改行中的值)。我们需要防止这种情况。 (行锁定是不可能的,因为表格是“myisam”)

我想到了几个解决方案:

在表单中设置旧值并将其与提交时的当前值进行比较

   <input name="money"><input type="hidden" name="old_money" value="10">

然后更新:

   $currentmoney=value_from_query("select money from mytable","money");
   if($currentmoney!=$_REQUEST["old_money"]){
      return "value changed to $currentmoney while you were editing it, are you sure you still want to change it?!??!?!?!?";
   }
   else{
     mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."' where user='$user_id'");
     return true;
   }

但可能存在以下情况:

  1. 用户需要将货币价值从9 $更改为10 $

  2. admin1将他的钱换成10 $

  3. 用户巧妙地花1美元,所以他现在的钱再次变成9美元!

  4. admin2将他的钱更改为10美元而没有任何警告。

  5. 在行

    中创建时间戳(updated_at列)设置

    与解决方案1相同。这样做的优势在于它不仅仅是简单的数据比较。我们可以肯定地说,在我们摆弄表格时是否更改了数据,或者没有。缺点 - 除非我们将其与解决方案1结合起来,否则我们无法跟踪确切更改的列

       <input type="hidden" name="formtimestamp" value="<? echo time();?>">
    

    然后在更新时:

       $query_add = ($overriden ? "" : " and updated_at>'".securevalue($_REQUEST["formtimestamp"])."'");
       if(mysql_affected_rows(mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."', updated_at=NOW() where user='$user_id' ".$query_add))==0){
          return "some values were changed by someone else while you were editing it, are you sure you still want to change it?!??!?!?!?";
       }
       else{
         return true;
       }
    

    使用对象/操作特定名称

    创建临时0长度文件

    在更新期间创建/锁定它,并检查它 更新前的存在/日期戳。

    更新前:

       $myfname="/tmp/user{$user_id}EDITMONEY.tmp";
       $timedifference=((time()-filectime($myfname)); //in seconds
       if(file_exists($myfname) and ($timedifference<60) and (!$overriden)){ // a minute difference
          $currentmoney=value_from_query("select money from mytable","money");
          return "money were edited by someone else $timedifference seconds ago and set to {$currentmoney}, are you sure you still want to change it?!??!?!?!?";
       }else{
          $fp = fopen("/tmp/user".intval($_REQUEST["user_id"])."EDITMONEY.tmp", "r+");         
          if (flock($fp, LOCK_EX)) { // do an exclusive lock
             mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."' where user='$user_id'")
            flock($fp, LOCK_UN); // release the lock
            return true;
         } else {
            return "Couldn't get the lock, it's possible that someone tried to execute query simultaneously!";
         }
    
       fclose($fp);
    
       }
    

    现在文件创建是我首选的方法,因为:

    1. 我认为创建本地文件比访问数据库更快。

    2. 我不需要再向表中添加一列(时间戳)

    3. 我可以轻松修改文件名以检查特定的列修改,即在mysqlupdate完成时创建文件“money_user {$ userid} _modified”。

    4. 这是对的还是我误解了什么?

4 个答案:

答案 0 :(得分:1)

我认为数据库的行级锁定不符合您在第一种方法中提到的情况。但我不认为文件创建比访问数据库系统更快。文件创建明显比数据库上的CRUD重。

所以,我建议采用类似的方法来记录表。

  • 每个表都有自己的主键(如pid
  • 当有人试图摆弄一行时,将表名和pid记录到带有时间戳的日志表中。
  • 在运行查询之前检查日志表。

答案 1 :(得分:1)

在你的情况下,我认为锁定是最好的方法。您可以使用MySQL锁:GET_LOCK, RELEASE_LOCK, IS_FREE_LOCK。我认为交易并不能保证在另一个进程对获取的数据执行任务时行不会改变。

虽然,您的特殊情况与传统意义上的锁定无关。恕我直言,您需要使用相应的凭据和说明记录您的交易,以便您的管理员可以阅读它们而不是复制相同的余额修改。锁定可以防止同时进行行修改,但不能防止配音时的故意更改。

答案 2 :(得分:1)

您可以在UPDATE操作的WHERE子句中指定旧值,然后查看受影响的行数:

鉴于

id  name          amount
--- ------------- ---------
1   Joe User      10

线程1执行

UPDATE accounts SET amount=9 WHERE id=1 AND amount=10;
=> Query Okay, 1 row(s) affected

线程2执行

UPDATE accounts SET amount=9 WHERE id=1 AND amount=10;
=> Query Okay, 0 row(s) affected

除此之外,我可能会先通过先将任务分配给各个管理员来实施排除,以减少浪费的时间。

答案 3 :(得分:0)

看看InnoDB和交易。它们更适合敏感变化(即平衡)。

数据库通常更好,因为它们是集中式解决方案。如果由于流量或一般工作负载而必须进行扩展,则同步这些文件并不容易。除非您不希望任何缩放和I / O速率都需要,否则就可以了。

请允许我提及两种可能的解决方案,您可能也在上面提到过这些解决方案。

您可以添加一个“assigned_id”,其中包含您的管理员帐户的ID以及时间戳,以便您的应用程序在其他人正在编辑时会显示警告。

另一种可能的解决方案是检查填写表单时是否进行了任何更改。这里可以使用last_edited时间戳。