[php+mysql]关于防止并发的小探究

其实是上个月的事儿,草稿在wp存了好久,今天终于有时间写完

有这样一个需求,用户用所得到的积分兑换礼品,于是我写了如下代码

[cc lang=’php’ ]
public function assignGift($money){
//判断钱数是否足够
if($this->getMoney()>=$money){
$this->saveGift();
//扣钱
$this->addMoney(-$money);
return true;
}
return false;
}[/cc]

于是测试工程师跟我说,我对这个操作做了1000次的并发,就可以用1份的积分兑换一大堆礼物,之后钱变成负数

我‘哦’了一声,改了一下

[cc lang=’php’ ]
public function assignGift($money){
//判断钱数是否足够
if($this->getMoney()>=$money){
//扣钱
$this->addMoney(-$money);
$this->saveGift();
return true;
}
return false;
}[/cc]

这个修改简直是弱爆了。显然结果是测试工程师又来跟我说,还是不行。

问题在哪儿呢,我们来看一下getMoney和addMoney里面写的什么
[cc lang=’php’ ]
public function getMoney(){
$money = $mysql->query(
‘SELECT Money
FROM user
WHERE UserId = ‘. $this->userId);
return $money[‘Money’];
}
public function addMoney($money){
$money = $mysql->query(
“UPDATE user
SET Money = Money+$money
WHERE UserId =”. $this->userId);
return true;
}[/cc]
问题来了吧(哪儿呢)。

在大量并发到来的时候,每个并发都先执行getMoney中的query,然后执行addMoney去减积分。执行顺序成了这样

process 1 :getMoney();

process n :getMoney();
process 1 :addMoney();

process n :addMoney();

于是就杯具了。

跟大家讨论了下,打算在用户提交请求的时候,顺便提交个token,验证后销毁。由于某些原因,服务器不能写session,所以用memcache替代

由于某些原因,服务器不能写session,所以用memcache替代
[cc lang=’php’ ]
public function assignGift($money){
if($_GET[‘token’] != $memcache->get(‘token’))
return false;
//清除缓存
$memcache->clear(‘token’);
//判断并扣钱
if($this->getMoney()>=$money){
……
}
return false;
}[/cc]
这次测试,并发时命中率低了不少,但是还是有额外命中的,肿么回事?

因为memcache的get和clear也要时间(尤其是实际环境memcache是分多台服务器,不在本地)

所以木有办法,最后加了一层mysql的事务解决(innoDB)

我以前没听说过还有事务这种东西,查了一下(链接自己查去= =)

简单说,就是在提交查询的时候加一条行锁,在整个事务流程完成前,不会再进行新的查询

[cc lang=’php’ ]
public function assignGift($money){
$mysql->query(
‘SET AUTOCOMMIT = 0 ;
START TRANSACTION ;’);
……
//判断并扣钱
if($this->getMoney()>=$money){
……
$mysql->query(
‘COMMIT ;
SET AUTOCOMMIT = 1;’);
return true;
}
$mysql->query(‘SET AUTOCOMMIT = 1;’);
return false;
}
public function getMoney(){
$money = $mysql->query(
‘SELECT Money FROM user
WHERE UserId = ‘. $this->userId
. ‘FOR UPDATE’);
return $money[‘Money’];
}[/cc]

另外求好用的wp贴代码的插件,codecolorer不好用,不能调整大小,不能设置临时的高亮(或许是我没去看源代码= =)

再另求好看的贴图的插件……