【问题标题】:Preventing simultaneous db table row access防止同时访问数据库表行
【发布时间】:2012-01-16 08:26:24
【问题描述】:

有时会发生我们支持团队中的两名管理员尝试对 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”。

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

【问题讨论】:

  • 行锁定的常用方法是在表中有两列,通常称为“lockwho”和“lockwhen” - 包含锁定行的用户的用户 ID,以及何时他们做到了。因此,当您给某人一个允许他们编辑行的页面时,请编辑带有用户 ID(用户名或用户表的更好的 FK)的“lockwho”列和过期的“lockwhen”,以防他们永远无法完成交易.然后在完成更新时,将“lockwho”设置为 NULL。如果其他人在锁定时尝试编辑该行 (lockwho != NULL),请不要让他们。

标签: php mysql timestamp file-locking simultaneous


【解决方案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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 根据我的阅读,行级锁定/事务仅适用于 innodb。我们的表格引擎是 myisam,没有计划改变它..
    • 不,GET_LOCK 等不是行锁定。它在MYSQL中被命名为锁系统。您可以将锁命名为“用户帐户 ID 或编号”。在您开始更改余额之前,您会尝试测试锁定是否免费。如果否 - 拒绝,则意味着在该行上进行了另一笔交易。如果它是免费的,请设置您的锁定并进行交易以更改余额。锁是服务器范围的(所有进程都看到每个锁)并且连接很长。因此,如果连接不是持久的,它们会自动释放。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2015-06-08
        • 2013-11-06
        • 2020-03-22
        • 1970-01-01
        • 1970-01-01
        • 2018-07-21
        • 1970-01-01
        • 2013-06-07
        • 1970-01-01
        相关资源
        最近更新 更多