0

有时我们的支持团队中的两个管理员正在尝试对 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 美元。

在行中创建时间戳(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 回答 4

1

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

因此,我建议对日志表采用类似的方法。

  • 每个表都有自己的主键(如pid
  • 当有人试图摆弄一行时,将表名和 pid 记录到带有时间戳的日志表中。
  • 在运行查询之前检查日志表。
于 2012-01-16T09:19:13.307 回答
1

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

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

于 2012-01-16T09:21:13.087 回答
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

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

于 2012-01-16T10:19:38.850 回答
0

看看 InnoDB 和事务。它们更适合敏感的变化(即平衡)。

数据库通常更好,因为它们是集中式解决方案。如果您因流量或一般工作负载而必须扩展,则同步这些文件并不容易。除非您不期望任何扩展需求并且 I/O 速率良好,否则没关系。

请允许我提及 2 个可能的解决方案,您可能在上面也提到过。

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

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

于 2012-01-16T09:06:45.200 回答