我们的支持团队中有时会有两名管理员试图对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;
}
但可能存在以下情况:
用户需要将货币价值从9美元更改为10美元
admin1将他的钱改为10$
用户聪明地花了1美元,所以他现在的钱又变成了9美元!
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);
}
目前,创建文件是我的首选方法,因为:
我认为创建本地文件比访问数据库更快。
我不需要在表中再添加一列(时间戳)
我可以很容易地修改文件名来检查特定的列修改,即在mysqlupdate完成后创建文件"money_user{$userid}_modified"。
是这样吗?还是我误解了什么?
您可以在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
除此之外,我可能会更早地实现排除,首先将任务分配给各个管理员,以减少浪费的时间。
在您的情况下,我认为锁定是最好的方法。您可以使用MySQL锁:GET_LOCK、RELEASE_LOCK、IS_FREE_LOCK。在我看来,事务并不能保证当另一个进程对提取的数据执行任务时,该行不会发生更改。
尽管如此,您的特殊情况与传统意义上的锁定无关。IMHO,你需要用相应的凭据和描述记录你的交易,这样你的管理员就可以阅读它们,而不是重复同样的余额修改。锁定可以防止同时修改行,但在配音的情况下不能防止故意更改。
我认为数据库的行级锁定不能满足您在第一个方法中提到的情况。但我不认为创建文件比访问数据库系统快,也不认为。文件创建显然比数据库上的CRUD更重。
所以,我建议对日志表采用类似的方法。
- 每个表都有自己的主键(如
pid
) - 当有人试图篡改一行时,将表名和pid记录到带有时间戳的日志表中
- 在运行查询之前,请检查日志表
看看InnoDB和事务。它们更适合敏感的变化(即平衡)。
数据库通常更好,因为它们是一个集中的解决方案。如果由于流量或通常的工作负载而不得不进行扩展,那么同步这些文件并不容易。除非你不希望有任何扩展需求,而且I/O速率很好,否则没关系。
请允许我提及两种可能的解决方案,你可能也在上面提到过。
您可以添加一个"assigned_id",其中包含您的管理员帐户id和时间戳,这样,如果其他人正在编辑应用程序,您的应用程序就会显示警告。
另一种可能的解决方案是在填写表格时检查是否有任何更改。此处可以使用last_eudited时间戳。